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

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Tech Education Fund 来接受捐赠。

介绍

TypeScript 对 JavaScript 开发人员如何为应用程序(尤其是 Web 应用程序)构建和编写代码进行了很多改进。 TypeScript 被定义为 JavaScript 的超集,其行为与 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

您将看到机器上安装的当前版本:

OutputVersion 3.4.5

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

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

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

在完成本节之前,应用程序需要从一个端口移动到另一个端口。 为此,您需要为您的 React 应用程序安装一个名为 React Router 的路由库及其相应的 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,它是一个基于 promise 的浏览器 HTTP 客户端,以帮助执行来自您将在应用程序中创建的不同组件的 HTTP 请求的过程:

yarn add axios

安装过程完成后,使用以下命令启动开发服务器:

yarn start

您的应用程序将在 http://localhost:3000 上运行。

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

第 2 步 — 创建 JSON 服务器

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

您可以使用 npm 或 yarn 在您的机器上安装 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 结构由一个客户对象组成,该对象分配了两个数据集。 每个客户由七个属性组成:id、description、first_name、last_name、email、phone 和address。

保存并退出文件。

默认情况下,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 的过程,您可以使用名为 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 提供支持的后端服务器配置了一个备用端口。 接下来,您将为您的应用程序构建可重用的组件。

第三步——创建可重用的组件

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

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

cd ./src/

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

mkdir components
cd components

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

mkdir customer
cd customer

现在创建两个名为 Create.tsx 和 Edit.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 包中导入了 React、axios 和其他路由所需的组件。 之后,您创建了两个名为 IValues 和 IFormState 的新接口。 TypeScript 接口 有助于定义应传递给对象的特定类型的值,并在整个应用程序中强制保持一致性。 这可以确保错误不太可能出现在您的程序中。

接下来,您将构建一个扩展 React.Component 的 Create 组件。 在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(“属性”的缩写)并使用 IFormState 类型的状态。 然后,在构造函数中,您初始化了 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() 方法。 这个 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_name、last_name、email、phone、address 的值, 和客户的 description。 每个输入字段都有一个方法 handleInputChanges() 在每次击键时运行,用它从输入字段获得的值更新 React state。 此外,根据应用程序的状态,名为 submitSuccess 的布尔变量将控制应用程序在创建新客户之前和之后显示的消息。

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

保存并退出Create.tsx。

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

打开 customer 文件夹中的 Edit.tsx 文件,首先添加以下内容以导入 React、axios,并定义 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 组件类似,您导入所需的模块并分别创建 IValues 和 IFormState 接口。 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() 方法,这是一个生命周期方法,在创建组件时会被调用。 该方法采用从路由参数中获得的 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 导入了 React、axios 和其他必需的组件。 您在 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 后,保存并退出。

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

要为您的应用程序包含引导程序和样式,请将 ./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 以从浏览器查看应用程序。 然后继续单击创建按钮并填写客户的详细信息。

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

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

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

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

结论

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

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

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