在本文中,我想向你们介绍 React Hooks
在列表页中的实践,主要是经过 useReducer
和 useEffect
来实现的。以及表达一下我对 React Hooks
的理解与思考。javascript
若是你还不知道 React
的这个新特性 React Hooks
,那么点击 Hooks 简介 ; 若是你想看直接查看最后的实现效果,请点击 仓库 。html
在平常的开发中,咱们经常会碰到一些列表页的开发需求。java
一个列表页的最基本的功能就是列表的展现,而列表的展现就须要不少 state
去管理,好比:列表数据、数据总数、当前页、展现条数等等。而这些 state
绝大部分是全部列表页的都通用的即有共同的逻辑。react
在以往的方案中尚未特别好的方案,能够共用这些逻辑代码。高阶组件 HOC
能够,但会引入组件层级过深的问题。(如读者有兴趣,可自行去了解高阶组件的用途,本文不深刻讨论)。ios
好消息是,React Hooks
就能帮助咱们完成这个心愿。git
咱们要是现实一个自定义 Hook useTable
,简单说明一下功能github
- 接收一个
url
,向外暴露{ onSearch, bind: { loading, dataSource, pagination: { current, pageSize, total }, onChange } }
。 这个是基于antd
开发的,因此bind
内的东西是绑定在antd
的Table
组件上的。- 在页面初始化时,自动请求数据
- 在页面卸载时,取消异步请求的后续操做
onChange
和onSearch
时自动请求数据- 在 loading 中,不会触发新的异步请求
好了话很少说,直接上代码。redux
// useTable.js
import { useReducer, useEffect } from 'react';
import axios from 'axios';
// action type
const DATA_CHANGE = 'DATA_CHANGE';
const STATE_CHANGE = 'STATE_CHANGE';
const DEFAULT_STATE = {
loading: false,
current: 1,
pageSize: 10,
total: 0,
order: false,
field: '',
dataSource: [],
params: {},
}
// 用做 useReducer 中的 reducer
const reducer = (state, action) => {
const { type, data: { dataSource, ...nextState } } = action;
switch (type) {
case STATE_CHANGE:
return {...state, ...nextState};
case DATA_CHANGE:
return {...state, dataSource, ...nextState};
default:
return state;
}
}
export default (url, initState = {}) => {
/** * useReducer 的概念和 redux 很像 * 会返回一个 dispatch 函数,调用的时候传给它一个 action * 相应的会有一个 reducer 函数,用于数据处理 */
const [{
loading, // 加载态
current, // 当前页
pageSize, // 一页多少条
total, // 总共多少条
order, // 排序方向
field, // 排序字段
dataSource, // 数据
params, // 额外搜索项
}, dispatch] = useReducer(reducer, {
...DEFAULT_STATE,
...initState,
});
// 获取数据的 hooks
useEffect(() => {
let cancel = false;
dispatch({ type: STATE_CHANGE, data: { loading: true } });
axios.post(
url,
{ current, pageSize, order, field, ...params },
).then(({ data, status }) => {
if (status === 200) return data;
}).then(({ data = [], total }) => {
!cancel && dispatch({ type: DATA_CHANGE, data: { dataSource: data, total }});
}).finally(() => dispatch({ type: STATE_CHANGE, data: { loading: false } }));
// 返回值时页面卸载以后调用的函数
return () => cancel = true;
}, [url, current, pageSize, order, field, params]); // 当这几个状态改变时自动调用函数
// 搜索事件
function onSearch(nextParams) {
// 点击搜索按钮 跳到第一页
!loading && dispatch({ type: STATE_CHANGE, data: { params: nextParams, current: 1 } });
}
// 变动事件
function onChange({ current, pageSize }, filters, { order = false, field = ''}) {
!loading && dispatch({ type: STATE_CHANGE, data: { current, pageSize, order, field }});
}
return {
onSearch,
bind: {
loading,
dataSource,
pagination: { current, pageSize, total },
onChange,
}
};
}
// UseHooksTable.js
import React, { Fragment } from 'react';
import { Table } from 'antd';
import SearchForm from './SearchForm';
import useTable from './useTable';
const url = 'https://www.easy-mock.com/mock/5cf8ead34758621a19eef994/getData';
function UseHooksTable () {
// 使用自定义 hook
const { onSearch, bind } = useTable(url);
const columns = [
{ title: '编号', dataIndex: 'id' },
{ title: '姓名', dataIndex: 'name' },
{ title: '年龄', dataIndex: 'age' },
{ title: '邮箱', dataIndex: 'email' },
{ title: '主页', dataIndex: 'url' },
{ title: '城市', dataIndex: 'city' },
];
return (
<Fragment>
<SearchForm onSearch={onSearch}/>
<Table
rowKey={'id'}
columns={columns}
{...bind}
/>
</Fragment>
);
}
export default UseHooksTable;
复制代码
在代码中的注释简单解释了一下代码,应该也没有什么难点。axios
到此为止,咱们应该思考 React Hooks
能够给咱们带来些什么。 为此,我额外的写了一个使用 class
方式实现的列表页,下面上代码antd
import React, { Component, Fragment } from 'react';
import { Table } from 'antd';
import axios from 'axios';
import SearchForm from './SearchForm';
const url = 'https://www.easy-mock.com/mock/5cf8ead34758621a19eef994/getData';
class UseClassTable extends Component {
state = {
loading: false,
current: 1,
pageSize: 10,
total: 0,
order: 0,
field: '',
params: {
name: '',
},
dataSource: [],
}
cancel = false;
columns = [
{ title: '编号', dataIndex: 'id', sorter: true },
{ title: '姓名', dataIndex: 'name', sorter: true },
{ title: '年龄', dataIndex: 'age', sorter: true },
{ title: '邮箱', dataIndex: 'email', sorter: true },
{ title: '主页', dataIndex: 'url', sorter: true },
{ title: '城市', dataIndex: 'city', sorter: true },
];
componentDidMount() {
this.getData();
}
componentWillUnmount() {
this.cancel = true;
}
// 搜索事件
handleSearch = (nextParams) => {
// 点击搜索按钮 跳到第一页
!this.state.loading && this.setState({ params: nextParams, current: 1 }, this.getData);
}
// 变动事件
handleTableChange = ({ current, pageSize }, filters, { order = false, field = ''}) => {
!this.state.loading && this.setState({ current, pageSize, order, field }, this.getData);
}
getData() {
const { current, pageSize, order, field, params } = this.state;
this.setState({ loading: true }, () => {
axios.post(
url,
{ current, pageSize, order, field, ...params },
).then(({ data, status }) => {
if (status === 200) return data;
}).then(({ data = [], total }) => {
!this.cancel && this.setState({ dataSource: data, total });
}).finally(() => this.setState({ loading: false }));
});
}
render() {
const {
loading, // 加载态
current, // 当前页
pageSize, // 一页多少条
total, // 总共多少条
dataSource, // 数据
} = this.state;
return (
<Fragment>
<SearchForm onSearch={this.handleSearch}/>
<Table
rowKey={'id'}
loading={loading}
columns={this.columns}
pagination={{ current, pageSize, total }}
dataSource={dataSource}
onChange={this.handleTableChange}
/>
</Fragment>
)
}
}
export default UseClassTable;
复制代码
咱们能够看到使用 Hooks
的方式,咱们能够把共有的逻辑封装到 Hooks
中,在全部有共有逻辑的页面都使用这样的 hook
,代码行数能够从原先的80行减小到30行,代码变得简单易懂,使用起来也很简单,提升的代码的复用性。
这应该就是 Hooks
的魅力。
咱们能够想象到,之后的社区会提供给咱们有趣的 Hooks
。
在咱们本身的开发中,也可使用 Hooks
来封装咱们的共有逻辑,效率能够大大提升。
并且最重要的是,Hooks
可让咱们的代码变得美观。
嗯,这很重要,哈哈哈哈哈哈。