React 组件的编写
在编写 React 组件时,有一些最佳实践需要遵循:
单一职责
就像函数一样,组件也应当遵循单一职责的原则,即每个组件只能负责一个功能。请阅读官方示例中的第一步。
将一个功能复杂的大组件拆分为多个职责单一的小组件可以获得以下好处:
- 可读性和可维护性的提升:小组件代码更短,且服务于同一目的;读者可以按层级理解代码。
- 可测试性的提升:小组件更容易做单元测试。
- 可复用性的提升:小组件可以在简单的调整和提取后被复用于其他场景。
- 性能的提升:state 的变化不再总是触发整个大组件的重渲染。
文件结构
拆分组件时在文件结构上的最佳实践是,假设原来有一个大组件是 Complex.tsx,拆分后它应变成一个文件夹:
1 | - Complex |
State 最小集
渲染组件所需要的数据有以下来源:外部传入的 prop、组件内部的 state、全局 state(比如 redux store)、组件 scope 外的常量,以及由前面这些基础数据计算得到的间接数据(即 Vue 中的 computed)。
新人易犯的错误是将间接数据作为 state 来维护,然后用 useEffect
去监听其他 state/prop 并重新计算该数据。
组件内部 state 应是一个最小集,不包含任何间接数据或常量。请阅读官方示例中的第三步。
Prop 的设计
Prop 的设计应尽量从组件本身的需求出发,而不是套用父组件已有的数据和方法。举例来说:
- 如果子组件需要的数据 c 是由父组件中的 a 和 b 计算而来的,那么应该为 c 设计一个 prop,在父组件中用 a 和 b 计算出 c 之后传给子组件,而不是为 a 和 b 设计两个 prop,在子组件内部再计算 c。这一条原则也可以理解为 prop 最小集。
- 如果子组件需要反向传递数据去设置父组件的 state(假设要传递的数据是 date),不要设计一个 setDate prop 然后把父组件的 setDate 直接传给子组件,而是设计一个 onDateChange prop,允许父组件设置一个 callback 去监听子组件的 date 变化。子组件不关心父组件是要 setDate 还是做什么,子组件只负责“广播”自己的数据变化“事件”。
组件内的代码组织
目前我们统一用 React hooks 编写组件,hooks 的一大优势就是允许我们将组件内的代码按业务逻辑而非性质分组:
1 | const Bad = () => { |
如果组件中有很多个逻辑分组,可能就是该组件需要进一步拆分的信号。
Context
当一个组件树中有很多不同层级的组件需要访问一批同样的数据时,完全用 prop 传递会导致中间层的组件为了往下传递而多出很多它自己并不关心的 prop,此时你通常需要 context 解决方案,请阅读官方文档。
当需要层层传递的数据本身非常简单时,也有除了 context 之外的解决方案。
表现与逻辑的分离
组件主要负责表现层(即 UI 层)的逻辑,因此复杂的数据处理逻辑原则上不属于组件的职责,应提取为工具函数而不是直接放一大段代码在组件内。这个职责分离的原则与上文组件拆分的原则在本质上是一致的,即每个单元应负责自己唯一的职责。