先看需求:
系统中的若干个页面需要展示表格数据,其中某些表格数据过多需要分页查询,而某些不用。
组件划分:
components: TableDisplay.jsx Pagination.jsx containers: TableContainer.jsx
TableDisplay
:表格数据展示组件,参数如下:
- data: Array类型,需要展示的表格数据
- loading: Boolean类型,控制表格是否加载状态
Pagination
:用于展示分页的组件,参数如下:
- total: Number类型,总数据量
- page: Number类型,当前页码
- pageSize: Number类型,每页展示的数据量
- onPageChange: Function类型,页码改变时的回调
- onPageSizeChange: Function类型,pageSize改变时的回调
TableContainer
:封装分页控制、加载表格数据等业务逻辑的容器型组件
1、 使用Class封装业务逻辑的组件
首先看下不使用hooks,使用传统的Class组件封装业务逻辑的代码:
import React from 'react';
import TableDisplay from 'components/TableDisplay';
import Pagination from 'components/Pagination';
import { getTableData } from 'apis'; //获取表格数据的api
export default class TableContainer extends React.Component {
state = {
data: {
tableData: [],
total: 0,
},
pagination: {
page: 1,
pageSize: 10
},
loading: false
}
componentDidMount() {
// 首次加载时默认查询第一页数据
this.loadTable();
}
handlePageChange: (page) => {
this.setState({
pagination: {
...this.state.pagination, page
}
});
this.loadTable();
}
handlePageSizeChange: (pageSize) => {
this.setState({
pagination: {
page: 1, //pageSize改变时,page自动跳到1
pageSize
}
});
this.loadTable();
}
loadTable: () => {
this.setState({
loading:true
});
// 调用APi获取数据
getTableData(this.state.pagination)
.then(({data}) => {
// 数据加载成功
this.setState({
data
});
})
.catch(error => {
console.log(error);
})
.finally(() => {
this.setState({
loading: false
});
});
}
render() {
const {data, pagination, loading} = this.state;
return (
<div>
<TableDisplay data={ data.tableData } loading={ loading } />
<Pagination
onPageChange={ this.handlePageChange }
onPageSizeChange={ this.handlePageSizeChange }
total={ data.total }
{ ...pagination } />
</div>
);
}
}
使用Class组件使得业务逻辑代码难以分割和复用;
2、使用带Hooks 的Function组件改写
import React, { useState, useEffect } from 'react';
import TableDisplay from 'components/TableDisplay';
import Pagination from 'components/Pagination';
import { getTableData } from 'apis'; //获取表格数据的api
export default () => {
// 表格数据
const [data, setData] = useState({
tableData: [],
total: 0
});
// 分页状态
const [pagination, setPagination] = useState({
page: 1,
pageSize: 10
});
// 加载状态
const [loading, setLoading] = useState(false);
useEffect(() => { //分页状态改变时,加载数据
setLoading(true);
getTableData(pagination)
.then(data => {
setTableData({
total: data.total,
data: data.data
});
})
.catch(error => {
console.log(error);
})
.finally(() => {
setLoading(false);
});
}, pagination);
const handlePageChange = page => setPagination({
page,
pageSize: pagination.pageSize
});
const handlePageSizeChange = pageSize => setPagination({
page: 1,
pageSize
});
return (
<div>
<TableDisplay data={ data.tableData } loading={ loading } />
<Pagination
onPageChange={ handlePageChange }
onPageSizeChange={ handlePageSizeChange }
total={ data.total }
{ ...pagination } />
</div>
);
}
到这一步,还没达到抽离可复用逻辑的目的
3、使用自定义Hooks分割并抽离业务逻辑
为了实现本文第一行中提到的需求,需要将表格数据加载和分页控制分割开来,使得分页这部分功能可插拔。
于是需要两个自定义Hook
hooks: --useTableDataLoader --usePagination
useTableDataLoader.js
代码如下:
import { useState, useEffect } from 'react';
export default (api, pagination) => {
const [data, setData] = useState({
total: 0,
tableData: []
});
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
api(pagination)
.then(data => {
setData(data);
})
.catch(error => {
console.log(error)
})
.finally(() => {
setLoading(false);
});
}, pagination);
return {
data, loading
};
}
HookuseTableDataLoader
需要两个参数:
api
:获取表格数据的apipagination
:分页状态,可选参数,当api不需要时可不传入,表格只会加载一次
返回一个对象:
data
: 请求的数据loading
:是否处于加载状态
usePagination.js
代码如下:
import { useState } from 'react';
export default () => {
const [pagination, setPagination] = useState({
page: 1,
pageSize: 10
});
return {
pagination,
setPage(page) {
setPagination({
page,
pageSize: pagination.pageSize
});
},
setPageSize(pageSize) {
setPagination({
page: 1,
pageSize
});
}
};
}
HookuseTableDataLoader
返回一个对象:
pagination
:分页信息setPage
:修改当前页码的方法setPageSize
:修改pageSize的方法
4、使用自定义的Hook实现带分页控制的表格业务组件
TableWithPaginationContainer.jsx
代码如下:
import React from 'react';
import TableDisplay from 'components/TableDisplay';
import Pagination from 'components/Pagination';
import { getTableData } from 'apis'; //获取表格数据的api
import useTableDataLoader from 'hooks/useTableDataLoader';
import usePagination from 'hooks/usePagination';
export default () => {
const { pagination, setPage, setPageSize } = usePagination();
const { data, loading } = useTableDataLoader(getTableData, pagination);
const { tableData, total } = data;
return (
<div>
<TableDisplay loading={loading} data={tableData}/>
<Pagination
onPageChange={ setPage }
onPageSizeChange={ setPageSize }
total={total}
{ ...pagination } />
</div>
);
}
5、无分页控制的表格业务组件
TableContainer.jsx
代码如下:
import React from 'react';
import TableDisplay from 'components/TableDisplay';
import { getTableData } from 'apis'; //获取表格数据的api
import useTableDataLoader from 'hooks/useTableDataLoader';
export default () => {
const { data, loading } = useTableDataLoader(getTableData);
const { tableData, total } = data;
return (
<TableDisplay loading={loading} data={tableData}/>
);
}
这样就实现了可复用逻辑代码的分割和抽离,并使得编写组件的代码更加简介明了!