如何使用React和TypeScript构建客户列表管理应用程序

TypeScript为JavaScript开发人员如何构建和编写应用程序代码(尤其是Web应用程序)带来了很多改进。 React是一个开源JavaScript库,开发人员使用它来为可伸缩的Web应用程序创建高端用户界面。在本教程中,您将创建一个客户列表管理应用程序,其中包含一个单独的REST API后端和一个使用React和TypeScript构建的前端。

作者选择技术教育基金作为Write for DOnations计划的一部分接受捐赠。

介绍

TypeScriptJavaScript开发人员如何构建和编写应用程序代码(尤其是Web应用程序)带来了很多改进。 定义为JavaScript的超集,TypeScript的行为与JavaScript相同,但具有额外的功能,旨在帮助开发人员构建更大,更复杂的程序,错误更少或没有错误。 TypeScript越来越受欢迎; 谷歌等主要公司采用Angular Web框架。 Nest.js后端框架也是使用TypeScript构建的。

作为开发人员提高工作效率的方法之一是能够尽快实现新功能,而无需担心破坏生产中的现有应用程序。 为了实现这一点,编写静态类型代码是许多经验丰富的开发人员采用的一种风格。 像TypeScript这样的静态类型编程语言强制每个变量与数据类型的关联; 例如字符串,整数,布尔值等。 使用静态类型编程语言的主要好处之一是类型检查在编译时完成,因此开发人员可以在很早的阶段看到代码中的错误。

React是一个开源JavaScript库,开发人员使用它来为可伸缩的Web应用程序创建高端用户界面。 使用React为单页应用程序构建的出色性能和动态用户界面使其成为开发人员的热门选择。

在本教程中,您将创建一个客户列表管理应用程序,其中包含一个单独的REST API后端和一个使用React和TypeScript构建的前端。 您将使用名为json-server的虚假REST API构建后端。 您将使用它快速设置CRUD(创建,读取,更新和删除)后端。 因此,您可以专注于使用React和TypeScript处理应用程序的前端逻辑。

先决条件

要完成本教程,您需要:

第1步 - 安装TypeScript并创建React应用程序

在此步骤中,您将使用节点包管理器( npm )在您的计算机上全局安装TypeScript包。 之后,您还将安装React及其依赖项,并通过运行开发服务器检查您的React应用程序是否正常工作。

首先,打开终端并运行以下命令以安装TypeScript:

npm install -g typescript

安装过程完成后,执行以下命令检查TypeScript的安装:

tsc -v

您将在计算机上看到当前版本:

Version 3.4.5

接下来,您将使用create-react-app工具安装React应用程序,以使用单个命令设置应用程序。 您将使用npx命令,它是npm 5.2+附带的软件包npm工具。 create-react-app工具内置了对使用TypeScript的支持,无需任何额外配置。 运行以下命令以创建并安装名为typescript-react-app的新React应用typescript-react-app

npx create-react-app typescript-react-app --typescript

上面的命令将创建一个名为typescript-react-app的新React应用typescript-react-app --typescript标志将React组件的默认文件类型设置为.tsx

在完成本节之前,应用程序将需要从一个端口移动到另一个端口。 为此,您需要为名为React Router的 React应用程序及其相应的TypeScript定义安装路由库。 您将使用yarn来安装此项目的库和其他包。 这是因为yarn更快,尤其是为React应用程序安装依赖项。 进入新创建的项目文件夹,然后使用以下命令安装React Router:

cd typescript-react-app
yarn add react-router-dom

您现在拥有React Router软件包,它将在您的项目中提供路由功能。 接下来,运行以下命令以安装React Router的TypeScript定义:

yarn add @types/react-router-dom

现在,您将安装axios ,这是一个基于承诺的浏览器HTTP客户端,以帮助执行您将在应用程序中创建的不同组件的HTTP请求:

yarn add axios

安装过程完成后,启动开发服务器:

yarn start

您的应用程序将在http://localhost:3000

反应应用程序主页

您已成功安装TypeScript,创建了新的React应用程序,并安装了React Router,以帮助您从应用程序的一个页面导航到另一个页面。 在下一节中,您将为应用程序设置后端服务器。

第2步 - 创建JSON服务器

在此步骤中,您将创建一个模拟服务器,您的React应用程序可以快速连接,以及使用其资源。 值得注意的是,此后端服务不适合生产中的应用程序。 您可以使用Nest.js,Express或任何其他后端技术在生产中构建RESTful API。 无论何时需要创建原型并模拟后端服务器, json-server都是一个有用的工具。

您可以使用npmyarn在您的计算机上安装json-server 这将使您可以在项目的任何目录中随时使用它。 打开一个新的终端窗口并运行此命令以在您仍在项目目录中时安装json-server

yarn global add json-server

接下来,您将创建一个JSON文件,其中包含将由REST API公开的数据。 对于此文件(您将创建)中指定的对象,将自动生成CRUD端点。 首先,创建一个名为server的新文件夹,然后移入其中:

mkdir server
cd server

现在,使用nano创建并打开一个名为db.json的新文件:

nano db.json

将以下内容添加到文件中:

/server/db.json
{
    "customers": [
        {
            "id": 1,
            "first_name": "Customer_1",
            "last_name": "Customer_11",
            "email": "customer1@mail.com",
            "phone": "00000000000",
            "address": "Customer_1 Address",
            "description": "Customer_1 description"
        },
        {
            "id": 2,
            "first_name": "Customer_2",
            "last_name": "Customer_2",
            "email": "customer2@mail.com",
            "phone": "00000000000",
            "address": "Customer_2 Adress",
            "description": "Customer_2 Description"
        }
    ]
}

JSON结构由一个客户对象组成,该客户对象分配了两个数据集。 每个客户包含七个属性: iddescriptionfirst_namelast_nameemailphoneaddress

保存并退出该文件。

默认情况下, json-server在端口3000上运行 - 这与运行React应用程序的端口相同。 为避免冲突,您可以更改json-server的默认端口。 为此,请移至应用程序的根目录:

cd ~/typescript-react-app

使用首选文本编辑器打开应用程序,并创建一个名为json-server.json的新文件:

nano json-server.json

现在插入以下内容以更新端口号:

/json-server.json
{
    "port": 5000
}

这将充当json-server的配置文件,它将确保服务器始终在其中指定的端口上运行。

保存并退出该文件。

要运行服务器,请使用以下命令:

json-server --watch server/db.json

这将在端口5000上启动json-server 如果您在浏览器中导航到http://localhost:5000/customers ,您将看到显示客户列表的服务器。

json-server显示的客户列表

要简化运行json-server ,可以使用名为server的新属性将package.json更新为scripts对象,如下所示:

/package.json
{
...
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "server": "json-server --watch server/db.json"
  },
...
}

保存并退出该文件。

现在,只要你想启动json-server ,你所要做的就是从终端运行yarn server

您已经创建了一个简单的REST API,您将用作此应用程序的后端服务器。 您还创建了一个客户JSON对象,该对象将用作REST API的默认数据。 最后,您为由json-server支持的后端服务器配置了备用端口。 接下来,您将为您的应用程序构建可重用的组件。

第3步 - 创建可重用组件

在本节中,您将为应用程序创建所需的React组件。 这将包括分别在数据库中创建,显示和编辑特定客户的详细信息的组件。 您还将为您的应用程序构建一些TypeScript接口。

首先,返回到运行React应用程序的终端,然后使用CTRL + C停止开发服务器。 接下来,导航到./src/文件夹:

cd ./src/

然后,在其中创建一个名为components的新文件夹并移动到新文件夹中:

mkdir components
cd components

在新创建的文件夹中,创建一个customer文件夹,然后移入其中:

mkdir customer
cd customer

现在创建两个名为Create.tsxEdit.tsx新文件:

touch Create.tsx Edit.tsx

这些文件是React可重用组件,它们将呈现表单并保留所有业务逻辑,分别用于创建和编辑客户的详细信息。

在文本编辑器中打开Create.tsx文件并添加以下代码:

/src/components/customer/Create.tsx
import * as React from 'react';
import axios from 'axios';
import { RouteComponentProps, withRouter } from 'react-router-dom';

export interface IValues {
    first_name: string,
    last_name: string,
    email: string,
    phone: string,
    address: string,
    description: string,
}
export interface IFormState {
    [key: string]: any;
    values: IValues[];
    submitSuccess: boolean;
    loading: boolean;
}

在这里,您已从React Router软件包导入路由所需的Reactaxios和其他必需组件。 之后,您创建了两个名为IValuesIFormState新接口。 TypeScript 接口有助于定义应传递给对象的特定值类型,并在整个应用程序中强制实现一致性。 这可以确保错误不太可能出现在您的程序中。

接下来,您将构建一个扩展React.ComponentCreate组件。 IFormState接口之后立即将以下代码添加到Create.tsx文件:

/src/components/customer/Create.tsx
...
class Create extends React.Component<RouteComponentProps, IFormState> {
    constructor(props: RouteComponentProps) {
        super(props);
        this.state = {
            first_name: '',
            last_name: '',
            email: '',
            phone: '',
            address: '',
            description: '',
            values: [],
            loading: false,
            submitSuccess: false,
        }
    }
}
export default withRouter(Create)

在这里,您已经在Typescript中定义了一个React组件。 在这种情况下, Create类组件接受RouteComponentProps类型的props (“属性”的RouteComponentProps ),并使用RouteComponentProps类型的状态。 然后,在构造函数中,初始化state对象并定义将表示客户的呈现值的所有变量。

接下来,在构造函数之后的Create类组件中添加这些方法。 您将使用这些方法来处理客户表单并处理输入字段中的所有更改:

/src/components/customer/Create.tsx
...
          values: [],
          loading: false,
          submitSuccess: false,
      }
  }

  private processFormSubmission = (e: React.FormEvent<HTMLFormElement>): void => {
          e.preventDefault();
          this.setState({ loading: true });
          const formData = {
              first_name: this.state.first_name,
              last_name: this.state.last_name,
              email: this.state.email,
              phone: this.state.phone,
              address: this.state.address,
              description: this.state.description,
          }
          this.setState({ submitSuccess: true, values: [...this.state.values, formData], loading: false });
          axios.post(`http://localhost:5000/customers`, formData).then(data => [
              setTimeout(() => {
                  this.props.history.push('/');
              }, 1500)
          ]);
      }

      private handleInputChanges = (e: React.FormEvent<HTMLInputElement>) => {
          e.preventDefault();
          this.setState({
              [e.currentTarget.name]: e.currentTarget.value,
      })
  }

...
export default withRouter(Create)
...

processFormSubmission()方法从应用程序状态接收客户的详细信息,并使用axios将其发布到数据库。 handleInputChanges()使用React.FormEvent获取所有输入字段的值,并调用this.setState()来更新应用程序的状态。

接下来,在handleInputchanges()方法之后立即在Create类组件中添加render() handleInputchanges()方法。 render()方法将显示表单以在应用程序中创建新客户:

/src/components/customer/Create.tsx
...
  public render() {
      const { submitSuccess, loading } = this.state;
      return (
          <div>
              <div className={"col-md-12 form-wrapper"}>
                  <h2> Create Post </h2>
                  {!submitSuccess && (
                      <div className="alert alert-info" role="alert">
                          Fill the form below to create a new post
                  </div>
                  )}
                  {submitSuccess && (
                      <div className="alert alert-info" role="alert">
                          The form was successfully submitted!
                          </div>
                  )}
                  <form id={"create-post-form"} onSubmit={this.processFormSubmission} noValidate={true}>
                      <div className="form-group col-md-12">
                          <label htmlFor="first_name"> First Name </label>
                          <input type="text" id="first_name" onChange={(e) => this.handleInputChanges(e)} name="first_name" className="form-control" placeholder="Enter customer's first name" />
                      </div>
                      <div className="form-group col-md-12">
                          <label htmlFor="last_name"> Last Name </label>
                          <input type="text" id="last_name" onChange={(e) => this.handleInputChanges(e)} name="last_name" className="form-control" placeholder="Enter customer's last name" />
                      </div>
                      <div className="form-group col-md-12">
                          <label htmlFor="email"> Email </label>
                          <input type="email" id="email" onChange={(e) => this.handleInputChanges(e)} name="email" className="form-control" placeholder="Enter customer's email address" />
                      </div>
                      <div className="form-group col-md-12">
                          <label htmlFor="phone"> Phone </label>
                          <input type="text" id="phone" onChange={(e) => this.handleInputChanges(e)} name="phone" className="form-control" placeholder="Enter customer's phone number" />
                      </div>
                      <div className="form-group col-md-12">
                          <label htmlFor="address"> Address </label>
                          <input type="text" id="address" onChange={(e) => this.handleInputChanges(e)} name="address" className="form-control" placeholder="Enter customer's address" />
                      </div>
                      <div className="form-group col-md-12">
                          <label htmlFor="description"> Description </label>
                          <input type="text" id="description" onChange={(e) => this.handleInputChanges(e)} name="description" className="form-control" placeholder="Enter Description" />
                      </div>
                      <div className="form-group col-md-4 pull-right">
                          <button className="btn btn-success" type="submit">
                              Create Customer
                          </button>
                          {loading &&
                              <span className="fa fa-circle-o-notch fa-spin" />
                          }
                      </div>
                  </form>
              </div>
          </div>
      )
  }
...

在这里,您创建了一个带有输入字段的表单,用于保存客户的first_namelast_nameemailphoneaddressdescription的值。 每个输入字段都有一个方法handleInputChanges() ,它在每次击键时运行,使用从输入字段获得的值更新React state 此外,根据应用程序的状态,名为submitSuccess的布尔变量将控制应用程序在创建新客户之前和之后显示的消息。

您可以在此GitHub存储库中查看此文件的完整代码。

保存并退出Create.tsx

现在您已将适当的逻辑添加到应用程序的Create组件文件中,您将继续为Edit组件文件添加内容。

打开customer文件夹中的Edit.tsx文件,首先添加以下内容以导入Reactaxios ,并定义TypeScript接口:

/src/components/customer/Edit.tsx
import * as React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import axios from 'axios';

export interface IValues {
    [key: string]: any;
}
export interface IFormState {
    id: number,
    customer: any;
    values: IValues[];
    submitSuccess: boolean;
    loading: boolean;
}

Create组件类似,您可以导入所需的模块并分别创建IValuesIFormState接口。 IValues接口定义输入字段值的数据类型,而您将使用IFormState声明应用程序的状态对象的预期类型。

接下来,在IFormState接口块之后直接创建EditCustomer类组件,如下所示:

/src/components/customer/Edit.tsx
...
class EditCustomer extends React.Component<RouteComponentProps<any>, IFormState> {
    constructor(props: RouteComponentProps) {
        super(props);
        this.state = {
            id: this.props.match.params.id,
            customer: {},
            values: [],
            loading: false,
            submitSuccess: false,
        }
    }
}
export default withRouter(EditCustomer)

此组件将RouteComponentProps<any>IFormState的接口作为参数。 您使用<any>RouteComponentProps因为每当React Router解析路径参数时,它不会进行任何类型转换以确定数据的类型是number还是string 由于您期望客户的uniqueId参数,因此使用any更安全。

现在在组件中添加以下方法:

/src/components/customer/Edit.tsx
...
    public componentDidMount(): void {
        axios.get(`http://localhost:5000/customers/${this.state.id}`).then(data => {
            this.setState({ customer: data.data });
        })
    }

    private processFormSubmission = async (e: React.FormEvent<HTMLFormElement>): Promise<void> => {
        e.preventDefault();
        this.setState({ loading: true });
        axios.patch(`http://localhost:5000/customers/${this.state.id}`, this.state.values).then(data => {
            this.setState({ submitSuccess: true, loading: false })
            setTimeout(() => {
                this.props.history.push('/');
            }, 1500)
        })
    }

    private setValues = (values: IValues) => {
        this.setState({ values: { ...this.state.values, ...values } });
    }
    private handleInputChanges = (e: React.FormEvent<HTMLInputElement>) => {
        e.preventDefault();
        this.setValues({ [e.currentTarget.id]: e.currentTarget.value })
    }
...
}

export default withRouter(EditCustomer)

首先,添加componentDidMount()方法,该方法是在创建组件时调用的生命周期方法。 该方法获取从route参数获取的id ,以将特定客户标识为参数,使用它从数据库中检索其详细信息,然后使用它填充表单。 此外,您还可以添加处理表单提交的方法,并处理对输入字段值所做的更改。

最后,为Edit组件添加render()方法:

/src/components/customer/Edit.tsx
...
    public render() {
        const { submitSuccess, loading } = this.state;
        return (
            <div className="App">
                {this.state.customer &&
                    <div>
                        < h1 > Customer List Management App</h1>
                        <p> Built with React.js and TypeScript </p>

                        <div>
                            <div className={"col-md-12 form-wrapper"}>
                                <h2> Edit Customer </h2>
                                {submitSuccess && (
                                    <div className="alert alert-info" role="alert">
                                        Customer's details has been edited successfully </div>
                                )}
                                <form id={"create-post-form"} onSubmit={this.processFormSubmission} noValidate={true}>
                                    <div className="form-group col-md-12">
                                        <label htmlFor="first_name"> First Name </label>
                                        <input type="text" id="first_name" defaultValue={this.state.customer.first_name} onChange={(e) => this.handleInputChanges(e)} name="first_name" className="form-control" placeholder="Enter customer's first name" />
                                    </div>
                                    <div className="form-group col-md-12">
                                        <label htmlFor="last_name"> Last Name </label>
                                        <input type="text" id="last_name" defaultValue={this.state.customer.last_name} onChange={(e) => this.handleInputChanges(e)} name="last_name" className="form-control" placeholder="Enter customer's last name" />
                                    </div>
                                    <div className="form-group col-md-12">
                                        <label htmlFor="email"> Email </label>
                                        <input type="email" id="email" defaultValue={this.state.customer.email} onChange={(e) => this.handleInputChanges(e)} name="email" className="form-control" placeholder="Enter customer's email address" />
                                    </div>
                                    <div className="form-group col-md-12">
                                        <label htmlFor="phone"> Phone </label>
                                        <input type="text" id="phone" defaultValue={this.state.customer.phone} onChange={(e) => this.handleInputChanges(e)} name="phone" className="form-control" placeholder="Enter customer's phone number" />
                                    </div>
                                    <div className="form-group col-md-12">
                                        <label htmlFor="address"> Address </label>
                                        <input type="text" id="address" defaultValue={this.state.customer.address} onChange={(e) => this.handleInputChanges(e)} name="address" className="form-control" placeholder="Enter customer's address" />
                                    </div>
                                    <div className="form-group col-md-12">
                                        <label htmlFor="description"> Description </label>
                                        <input type="text" id="description" defaultValue={this.state.customer.description} onChange={(e) => this.handleInputChanges(e)} name="description" className="form-control" placeholder="Enter Description" />
                                    </div>
                                    <div className="form-group col-md-4 pull-right">
                                        <button className="btn btn-success" type="submit">
                                            Edit Customer </button>
                                        {loading &&
                                            <span className="fa fa-circle-o-notch fa-spin" />
                                        }
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                }
            </div>
        )
    }
...    

在这里,您创建了一个表单来编辑特定客户的详细信息,然后使用您的应用程序状态获得的客户详细信息填充该表单中的输入字段。 Create组件类似,对所有输入字段所做的更改将由handleInputChanges()方法处理。

您可以在此GitHub存储库中查看此文件的完整代码。

保存并退出Edit.tsx

要查看在应用程序中创建的完整客户列表,您将在./src/components文件夹中创建一个新组件,并将其命名为Home.tsx

cd ./src/components
nano Home.tsx

添加以下内容:

/src/components/Home.tsx
import * as React from 'react';
import { Link, RouteComponentProps } from 'react-router-dom';
import axios from 'axios';

interface IState {
    customers: any[];
}

export default class Home extends React.Component<RouteComponentProps, IState> {
    constructor(props: RouteComponentProps) {
        super(props);
        this.state = { customers: [] }
    }
    public componentDidMount(): void {
        axios.get(`http://localhost:5000/customers`).then(data => {
            this.setState({ customers: data.data })
        })
    }
    public deleteCustomer(id: number) {
        axios.delete(`http://localhost:5000/customers/${id}`).then(data => {
            const index = this.state.customers.findIndex(customer => customer.id === id);
            this.state.customers.splice(index, 1);
            this.props.history.push('/');
        })
    }
}

在这里,您已从React Router导入Reactaxios和其他必需组件。 您在Home组件中创建了两个新方法:

  • componentDidMount() :应用程序在挂载组件后立即调用此方法。 它的职责是检索客户列表并使用它更新主页。
  • deleteCustomer() :此方法将接受id作为参数,并将从数据库中删除使用该id标识的客户的详细信息。

现在添加render()方法以显示包含Home组件的客户列表的表:

/src/components/Home.tsx
...
public render() {
        const customers = this.state.customers;
        return (
            <div>
                {customers.length === 0 && (
                    <div className="text-center">
                        <h2>No customer found at the moment</h2>
                    </div>
                )}
                <div className="container">
                    <div className="row">
                        <table className="table table-bordered">
                            <thead className="thead-light">
                                <tr>
                                    <th scope="col">Firstname</th>
                                    <th scope="col">Lastname</th>
                                    <th scope="col">Email</th>
                                    <th scope="col">Phone</th>
                                    <th scope="col">Address</th>
                                    <th scope="col">Description</th>
                                    <th scope="col">Actions</th>
                                </tr>
                            </thead>
                            <tbody>
                                {customers && customers.map(customer =>
                                    <tr key={customer.id}>
                                        <td>{customer.first_name}</td>
                                        <td>{customer.last_name}</td>
                                        <td>{customer.email}</td>
                                        <td>{customer.phone}</td>
                                        <td>{customer.address}</td>
                                        <td>{customer.description}</td>
                                        <td>
                                            <div className="d-flex justify-content-between align-items-center">
                                                <div className="btn-group" style={{ marginBottom: "20px" }}>
                                                    <Link to={`edit/${customer.id}`} className="btn btn-sm btn-outline-secondary">Edit Customer </Link>
                                                    <button className="btn btn-sm btn-outline-secondary" onClick={() => this.deleteCustomer(customer.id)}>Delete Customer</button>
                                                </div>
                                            </div>
                                        </td>
                                    </tr>
                                )}
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        )
    }
...

在此代码块中,您将应用程序状态中的客户列表作为数组进行检索,对其进行迭代,并在HTML表中显示它。 您还可以添加customer.id参数,该方法用于从列表中标识和删除特定客户的详细信息。

保存并退出Home.tsx

您已经为使用此应用程序创建的所有组件采用了静态类型原则,方法是通过使用接口来定义组件和道具的类型。 这是将TypeScript用于React应用程序的最佳方法之一。

有了这个,您已经完成了为应用程序创建所有必需的可重用组件。 您现在可以使用指向您目前创建的所有组件的链接更新应用程序组件。

第4步 - 设置路由并更新应用程序的入口点

在此步骤中,您将从React Router包导入必要的组件,并将App组件配置为根据加载的路由呈现不同的组件。 这将允许您浏览应用程序的不同页面。 一旦用户访问路由,例如/create ,React Router将使用指定的路径来呈现定义的相应组件内的内容和逻辑以处理此类路由。

导航到./src/App.tsx

nano App.tsx

然后用以下内容替换其内容:

/src/App.tsx
import * as React from 'react';
import './App.css';
import { Switch, Route, withRouter, RouteComponentProps, Link } from 'react-router-dom';
import Home from './components/Home';
import Create from './components/customer/Create';
import EditCustomer from './components/customer/Edit';

class App extends React.Component<RouteComponentProps<any>> {
  public render() {
    return (
      <div>
        <nav>
          <ul>
            <li>
              <Link to={'/'}> Home </Link>
            </li>
            <li>
              <Link to={'/create'}> Create Customer </Link>
            </li>
          </ul>
        </nav>
        <Switch>
          <Route path={'/'} exact component={Home} />
          <Route path={'/create'} exact component={Create} />
          <Route path={'/edit/:id'} exact component={EditCustomer} />
        </Switch>
      </div>
    );
  }
}
export default withRouter(App);

您从React Router软件包导入了所有必需的组件,并且还导入了可重用组件,以便创建,编辑和查看客户的详细信息。

保存并退出App.tsx

./src/index.tsx文件是此应用程序的入口点并呈现应用程序。 打开此文件并将React Router导入其中,然后将App组件包装在BrowserRouter

/src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom'; 
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
    <BrowserRouter>
        <App />
    </BrowserRouter>
    , document.getElementById('root')
);
serviceWorker.unregister();

React Router使用BrowserRouter组件使您的应用程序了解导航,例如历史记录和当前路径。

完成Index.tsx编辑Index.tsx ,保存并退出。

最后,您将使用Bootstrap为您的应用程序添加一些样式。 Bootstrap是一种流行的HTML,CSS和JavaScript框架,用于在Web上开发响应式,移动优先项目。 它允许开发人员构建一个吸引人的用户界面,而无需编写太多的CSS。 它配备了一个响应式网格系统,使网页具有适用于所有设备的完整外观。

要为您的应用程序包含Bootstrap和样式,请使用以下内容替换./src/App.css的内容:

/src/App.css
@import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';

.form-wrapper {
  width: 500px;
  margin: 0 auto;
}
.App {
  text-align: center;
  margin-top: 30px;
}
nav {
  width: 300px;
  margin: 0 auto;
  background: #282c34;
  height: 70px;
  line-height: 70px;
}
nav ul li {
  display: inline;
  list-style-type: none;
  text-align: center;
  padding: 30px;
}
nav ul li a {
  margin: 50px 0;
  font-weight: bold;
  color: white;
  text-decoration: none;
}
nav ul li a:hover {
  color: white;
  text-decoration: none;
}
table {
  margin-top: 50px;
}
.App-link {
  color: #61dafb;
}
@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

您已在此处使用Bootstrap通过为其提供默认布局,样式和颜色来增强应用程序的外观。 您还添加了一些自定义样式,尤其是导航栏。

保存并退出App.css

在本节中,您已将React Router配置为根据用户访问的路径呈现相应的组件,并添加了一些样式以使应用程序对用户更具吸引力。 接下来,您将测试为应用程序实现的所有功能。

第5步 - 运行您的应用程序

现在您已经使用React和TypeScript通过创建多个可重用组件来设置此应用程序的前端,并且还使用json-server构建了一个REST API,您可以运行您的应用程序。

导航回项目的根文件夹:

cd ~/typescript-react-app

接下来运行以下命令来启动您的应用:

yarn start

注意:确保您的服务器仍在另一个终端窗口中运行。 否则,启动它: yarn server

导航到http://localhost:3000以从浏览器查看应用程序。 然后继续单击“ 创建”按钮并填写客户的详细信息。

创建客户页面

在输入字段中输入适当的值后,单击“ 创建客户”按钮以提交表单。 完成创建新客户后,应用程序会将您重定向回主页。

查看客户页面

单击任何行的“ 编辑客户”按钮,您将被定向到承载该行上相应客户的编辑功能的页面。

编辑客户页面

编辑客户的详细信息,然后单击“ 编辑客户”以更新客户的详细信息。

您运行应用程序以确保所有组件都正常工作。 使用应用程序的不同页面,您已创建并编辑了客户条目。

结论

在本教程中,您使用ReactTypeScript构建了一个客户列表管理应用程序。 本教程中的过程偏离了使用JavaScript作为使用React构建和构建应用程序的传统方法。 您已经利用TypeScript的优势来完成这个前端重点教程。

要继续开发此项目,您可以将模拟后端服务器移动到生产就绪的后端技术,如ExpressNest.js。 此外,您可以通过使用Passport.js身份验证库等不同工具添加更多功能(如身份验证和授权)来扩展本教程中构建的内容。

您可以在GitHub上找到该项目的完整源代码。