如何用 ts 来编写 react 中的事件处理和数据请求?
功能
在 employee/QueryForm 组件中,升级 Input、Select 为受控组件,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,
};
}
为 Input 和 Select 绑定 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,启动服务:
将请求代理到本地:
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: 渲染逻辑
});
};
}
请求成功发送,并返回了 mock 数据。