练练手! TS + React 小项目(03 事件处理 & 数据请求)

2,251 阅读3分钟

如何用 ts 来编写 react 中的事件处理和数据请求?

功能

employee/QueryForm 组件中,升级 InputSelect 为受控组件,Button 添加数据请求事件,以渲染员工信息列表。

实现

抽离接口文件

我们在 src/interface 下,存放所有与后端交互的数据请求类型。比如, employee/QueryForm 中的状态(state)就是向后端发送的数据请求的格式,可以单独抽离成接口。

/*
 ** src/interface/employee.ts
 */

export interface EmployeeRequest {
  name: string;
  departmentId: number | undefinded;
}

// 单条数据
interface EmployeeInfo {
  id: number;
  key: number;
  name: string;
  department: string;
  hiredate: string;
  level: string;
}

export type EmployeeResponse = EmployeeInfo[] | undefinded;

定义接口文件的好处有二:

  • 有利于项目维护,后续开发人员对格式类型一目了然,使用接口的变量会有自动提示功能。
  • 类型检查,对于未考虑到的边界情况,ts 可以全面涵盖,报错提醒。

事件处理

初始化组件状态

import { EmployeeRequest } from "../../interface/employee";

// class QueryForm extends Component<{}, EmployeeRequest>
class QueryForm extends Component<{}, EmployeeRequest> {
  state = {
    name: "",
    departmentId: undefined,
  };
}

InputSelect 绑定 value、添加事件

class QueryForm extends Component<{}, EmployeeRequest> {
  state = {
    name: "",
    departmentId: undefined,
  };
  /*
   ** 根据 `Antd` 官方接口定义,`Input` 的 `onChange` 回调会返回一个事件类型
   ** onChange?: ((event: React.ChangeEvent<HTMLInputElement>) => void) | undefined
   */
  handleNameChange = (e: React.FormEvent<HTMLInputElement>) => {
    this.setState({ name: e.currentTarget.value });
  };

  /*
   ** onChange?: ((value: number, option: OptionsType | OptionData | OptionGroupData) => void) | undefined
   */
  handleDepartmentChange = (value: number) => {
    this.setState({ departmentId: value });
  };

  render() {
    return (
      <>
        <Form layout="inline">
          <Form.Item>
            <Input
              placeholder="姓名"
              style={{ width: 120 }}
              allowClear
              value={this.state.name}
              onChange={this.handleNameChange}
            />
          </Form.Item>
          <Form.Item>
            <Select
              placeholder="部门"
              style={{ width: 120 }}
              allowClear
              value={this.state.departmentId}
              onChange={this.handleDepartmentChange}
            >
              <Option value={1}>技术部</Option>
              <Option value={2}>运营部</Option>
              <Option value={3}>市场部</Option>
            </Select>
          </Form.Item>
          <Form.Item>
            <Button type="primary">查询</Button>
          </Form.Item>
        </Form>
      </>
    );
  }
}

封装 Axios

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

/*
 ** src/utils/request.ts
 */

import originAxios from "axios";
import { message } from "antd";

// 创建自定义实例,指定请求超出时间为20s
const axios = originAxios.create({ timeout: 20000 });

// 添加响应拦截器
axios.interceptors.response.use(
  function (response) {
    /*
      返回数据格式:

      successful response:
      {"flag": 0, "data": ""}

      unsuccessful response:
      {"flag": 1, "msg": "error"}
     */
    if (response.data && response.data.flag === 1) {
      const errorMsg = response.data.msg;
      message.error(errorMsg);
      return Promise.reject(errorMsg);
    }
    return response.data;
  },
  function (error) {
    return Promise.reject(error);
  }
);

// 对 get & post 进行封装
export function get(url: string, data: any) {
  axios.get(url, { params: data });
}
export function post(url: string, data: any) {
  axios({ method: "post" }, url, data);
}

因为 axios.post 默认会将 js objects 序列化成 json 格式。所以选用 axios({ method: "post" }, url, data);,这样后端可以省略 json 解析步骤。

定义请求路径

/*
 ** src/constans/urls.ts
 */
export const GET_EMPLOYEE_URL: string = "/api/employee/getEmployee.action";

mock 处理

在做前后端分离开发时,我们需要对请求做 mock 处理 -- 把发送给后端的请求代理到本地的 mock server

在根目录下新建 mock 文件夹,启动一个以 mock 为根目录的 API Server,为我们提供 mock 服务。

|--mock
    |--employee
        |--getEmployee.json
/*
 ** mock/employee/getEmployee.json
 */
{
  "flag": 0,
  "data": [
    {
      "id": 1,
      "key": 1,
      "name": "小明",
      "department": "技术部",
      "hiredate": "2019-07-01",
      "level": "1级"
    },
    {
      "id": 2,
      "key": 2,
      "name": "小莉",
      "department": "产品部",
      "hiredate": "2017-07-01",
      "level": "2级"
    }
  ],
  "msg": "Error"
}
/*
 ** package.json
 */
{
  "script": {
    ...
    "server": "cd mock &$ hs -p 4000 -a localhost"
  }
}

server 脚本说明:

  • 进入 mock 目录,
  • 在当前目录下启动 http-server,api 端口:4000,服务名称:localhost

然后,新开一个 terminal,启动服务:

1.png

将请求代理到本地:

create-react-app 在启动时,会自动调用 setupProxy.js

/*
 ** src/setupProxy.js
 */

const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = function (app) {
  app.use(
    createProxyMiddleware("/api/**/*.action", {
      target: "http://localhost:4000",
      pathRewrite(path) {
        return path.replace("/api", "/").replace(".action", ".json");
      },
    })
  );
};

在开发环境中,请求会自动代理到本地;生产环境中,也不需要修改代码。

发送请求

Button 绑定查询事件。

我们不仅在点击 Button 时,触发查询请求,在页面初始化时( componentDidMount )也需要触发。

import { get } from "../../utils/request";
import { GET_EMPLOYEE_URL } from "../../constans/urls";

class QueryForm extends Component<{}, EmployeeRequest> {
  handleSubmit = () => {
    this.queryEmployee(this.state);
  };
  componentDidMount() {
    this.queryEmployee(this.state);
  }
  queryEmployee = (param: EmployeeRequest) => {
    get(GET_EMPLOYEE_URL, param).then((response) => {
      // TODO: 渲染逻辑
    });
  };
}

2.png

请求成功发送,并返回了 mock 数据。

React & TS 系列