业务组件设计 ——个人实践
前言
初衷
- 抽离通用逻辑,避免重复搬砖
组件分类
- 基础组件:提供基本功能,适用通用场景
- 业务组件:在基础组件的基础上,加入特定功能,适用特定场景
组件评价标准
集成复杂度:业务侧使用该组件的复杂程度
越高越复杂
控制反转(IoC):代码耦合程度
越高耦合度越低
业务组件开发的心路历程
Stage 1: Hooks 时代之前
组件
一个受控组件,外部数据为 “单一真实来源”
class SearchTable extends React.Component {
// 各种交互逻辑...
render () {
const {
dataSource,
loading,
onPaginationChange,
//...一堆原生组件的 props 和当前组件的 props
} = this.props
return (
<Fragment>
<Form />
<Table dataSource={dataSource} loading={loading} />
</Fragment>
)
}
}
业务侧
state = {
loading,
dataSource
}
const fetchData = async () => {
state.loading = true
await fetch()
state.loading = false
state.dataSource = []
}
const handleChangePage = () => {
fetchData()
}
const doSotine = () => {
ref.doSomething()
}
return (
<SearchTable
ref={ref}
loading
dataSource
onChangePage={handleChangePage}
/>
)
优点
- 少写点内部通用逻辑
缺点
- 数据交互都是放在业务侧的,仍旧会有许多样板代码
- 需要通过 ref 来操作组件事件
- 组件臃肿,随着功能增强,越来越难维护
评价
集成复杂度:中
控制反转:中
Stage2: Hooks 初期时代
组件(Hooks)
一个集成了数据来源逻辑,功能逻辑,已经传好参数的组件的hooks
// 基础组件
const BaseSearchTable = ({ ...props }) => <Table {...props} />
// hooks
const useSearchTable = ({ fetchMethod, tableProps }) => {
const [dataSource, setDataSource] = useState()
const [loading, setLoaidng] = useState()
const fetchData = await () => {
setData()
//... 操作逻辑
}
// onPageChange
// deleteRow
// updateRow
// 一堆集成好的业务逻辑
const SearchTable = ({ props }) => {
<BaseSearchTable
dataSource={dataSource}
loading={loading}
{...tableProps}
{...props}
/>
}
return {
SearchTable,
dataSource,
// ... 返回一些需要的方法、数据
}
}
业务侧
const {
SearchTable,
deleteRow
} = useSearchTable({
fetchMethod: 'xxx',
rowKey: 'xxx',
// 其他配置
})
const handleDoSomething = () => {
deleteRow()
}
return (
<SearchTable />
)
优点
- 只要知道参数是干什么用的,使用起来就非常方便。
缺点
- hooks 逻辑肿,随着功能增强,越来越难维护。
- 又可以向 hooks 传参、又可以向导出的组件传参,容易出问题。
- hooks 应该是干净的,抽象的,而这里不仅复杂,还暴露了组件。
评价
集成复杂度:低
控制反转:低
Stage 3:现在
组件
一个只处理数据、内部逻辑的,返回组件所需 props 的 hooks
一个通过bind ,返回组件方法的 hooks
一个现成的集成好上面 hooks 的组件
// props hooks
const useSearchTableProps = ({ fetchMethod }) => {
const fetchData = () => {}
// 一堆业务操作逻辑、方法
// deleteRow
// updateRow
return {
dataSource,
loaing,
deleteRow,
updateRow,
}
}
// methods hooks
const useSearchTable = () => {
// to bind
}
// 业务组件
const SearchTable = ({ props }) => {
const {
dataSource,
loaing,
} = useSearchTableProps({
...props
})
// bind useSearchTable({
// deleteRow,
// updateRow,
// dataSource,
// })
return (
<BaseSearchTable
dataSource,
loaing
// ...other props
/>
)
}
业务侧
可以直接使用集成好的(也可以单用 hook,自己定义功能)
const searchTable = useSearchTable()
const doSomething = () => {
searchTable.deleteRow()
}
console.log(searchTable.dataSource)
return (
<SearchTable
searchTable={searchTable} // bind hooks
fetchMethod="xxx"
{...otherProps}
/>
)
优点
使用方便。
因为通过 hooks 解耦,你可以只有一个 hooks,然后定制一些你想要的,手动集成基础组件,自定义程度提升。
缺点
- 实现复杂度变高
- 缺乏可见性,需要很好的理解 hooks 的内部逻辑
评价
(只评价集成好的 SearchTable,而非 hooks)
集成复杂度:低+
控制反转:低
总结
- 业务组件:应该是低集成复杂度,提高业务编写效率,但是相对的代价是控制反正程度也会低
- 想要更多的控制权,就会增加业务集成复杂度
- 基础组件库:会为了更好的通用性,更低的耦合度,带来高的复杂度