Skip to main content

业务组件设计 ——个人实践

前言

初衷

  • 抽离通用逻辑,避免重复搬砖

组件分类

  • 基础组件:提供基本功能,适用通用场景
  • 业务组件:在基础组件的基础上,加入特定功能,适用特定场景

组件评价标准

  • 集成复杂度:业务侧使用该组件的复杂程度

    越高越复杂

  • 控制反转(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)

集成复杂度:低+

控制反转:低

总结

  • 业务组件:应该是低集成复杂度,提高业务编写效率,但是相对的代价是控制反正程度也会低
  • 想要更多的控制权,就会增加业务集成复杂度
  • 基础组件库:会为了更好的通用性,更低的耦合度,带来高的复杂度