title: '业务组件设计个人实践' description: '个人实践向,业务组件库设计思路' pubDate: 2021-03-15 tags: ['实践']

前言

初衷

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

组件分类

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

组件评价标准

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

    越高越复杂

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

集成复杂度:低+

控制反转:低

总结

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