项目.png阅读本文需一点react hook基础(react的16.7beta版、16.8正式版,文中涉及useState,useEffect最基本用法)
分析到这个系统具有多个这种相同结构的页面,就是组合搜索框(Searchgroup)和列表(Table)两大组合式组件,都是对antd的二次封装,在此不介绍如何封装,先看原本业务代码
// xx/index.js 容器页面
...
...
const FETCH_API = 'xxx/xxx';
class Index extends Component {
// 存搜索框的值
state = {
values: {},
selectRowKeys: [], // 列表选择的key数组
}
// 分页请求
fetchList = (page = 1, pageSize = 10) => {
const { values } = this.state
this.props.dispatch({
type: FETCH_API ,
payload: {
page,
pageSize,
...values,
}
})
}
// 查询触发
handleSearch = values => {
this.setState({values}, this.fetchList)
}
render() {
const { list, page, loading } = this.props
const { selectRowKeys } = this.state
const rowSelection = {
selectRowKeys,
onChange: selectRowKeys => this.setState({selectRowKeys })
}
return (
<>
<Card>
<Searchgroup
config={config}
handleSearch={this.handleSearch}
/>
</Card>
<Card>
<Table
columns={columns}
dataSource={list}
pagination={page}
loading={loading }
rowSelection={rowSelection}
rowKey='xxxId'
/>
</Card>
</>
)
}
}
export default connect(({ test, loading }) => ({
list: test.list,
page: test.page,
loading: loading.effects[FETCH_API],
}))(Index);
分析了好几个页面(基本全部容器组件),都存在几个重复的逻辑
- 查询动作,handleSearch,用来更新state中的values,回调作为fetchList参数
- 根据state的values拉取接口返回分页(fetchList),effect后触发到各个modal的分发,引起Table组件loading,dataSource,pagination等属性变化
- 列表的所选id还有触发动作(rowSelection)
...
我会想能不能写少点代码,只要传入相应配置就可以达到逻辑复用,这里,我选择用函数组件(+hook)代替类组件,因为
1.类组件比较倾向于面向生命周期编写,业务逻辑中相同一个方法很大可能会重复出现在不同生命周期,例如componentDidMount,componentDidUpdate,componentWillUnmount很容易会出现相同的方法,而用hook倾向于面向业务编写,一般情况一个useEffect搞定
2.class的热重载不稳定(有待考究)
...
PS: react16.7+的函数式组件才开始有处理内部state能力,之前的版本都是纯props组件,还有hook只能写在函数组件里或者自定义hook里
下面是自定义hook代码
// hooks.js
import { useState, useEffect } from 'react';
export const usePageList = ({ list, page, loading }, api) => {
// 注意useState的值顺序不能变,因为hook内部的值是用链表存储的,例如最好不要在外围加条件判断
const [values, setValues] = useState({});
const [selectRowKeys, setSelectRowkeys] = useState([]);
const handleSearch = values => {
setValues(values);
};
const fetch = (page = 1, pageSize = 10) => {
// 为了封装性,直接用全局的dispatch,不用手动传进来了
window.g_app._store.dispatch({
type: api,
payload: {
page,
pageSize,
...values
}
});
};
// 根据values变化触发拉取分页和重置选择的动作
useEffect(() => {
fetch()
setSelectRowkeys([])
}, [values]);
// 搜索框的属性
const searchProps = {
handleSearch,
};
// 分页表格的属性
const tableProps = {
dataSource: list,
pagination: page,
onChange: ({ current, pageSize }) => fetch(current, pageSize),
loading,
rowSelection: {
selectRowKeys,
onChange: k => setSelectRowkeys(k),
},
};
return {
values,
searchProps,
tableProps,
selectRowKeys,
};
};
用法
// xx/index.js 容器页面
...
import { usePageList } from 'hooks';
...
const FETCH_API = 'xxx/xxx';
// props其实可以干净点,只写{list,page,loading}就够用,但为了以后拓展和少些点代码(还有懒)的原 因,直接一个大props完事
const Index = props => {
// 由自定义hook返回几个属性,直接提供给相应子组件
const { values, searchProps, tableProps, selectRowKeys } = usePageList(props, FETCH_API);
return (
<>
<Card>
<Searchgroup
config={config}
{...searchProps}
/>
</Card>
<Card>
<Table
columns={columns}
rowKey="xxxId"
{...tableProps}
/>
</Card>
</>
);
};
export default connect(({ test, loading }) => ({
list: test.list,
page: test.page,
loading: loading.effects[FETCH_API],
}))(Index);
完事,业务逻辑直接封装好,只需抽离出个性部分就就可以了,省去大量代码,具有了封装性还留着可拓展的可能性。其实里面还可以用多个自定义hook组合,例如原本还想把selectRowKeys逻辑部分抽出单独hook处理,但为了不过度抽象就先放下了,毕竟适合业务和有高的开发体验才是最重要的
网友评论