# 如何设计 React 组件

这里是考察是否了解 React 组件的设计模式

# React 组件的分类

  • 展示组件:把只做展示、独立运行、不额外增加功能的组件,称为”哑组件“或”无状态组件“,还有一种叫法是 ”展示组件“
    • 代理组件:例如:用第三方库 antd button 在封装一层默认值类型
    • 样式组件:例如动态的 class 名称
    • 布局组件:相同的可复用的布局 例如:样式,或 布局组件
  • 状态组件:把处理业务逻辑与数据状态的组件称为有”状态组件“,或”灵巧组件“,灵巧组件一定包含至少一个灵巧组件或者展示组件。
    • 容器组件:几乎不可复用,主要是拉取数据与组合组件。如:调用服务端数据,并做相关逻辑操作。
    • 高阶组件:React中复用组件逻辑的高级技术。 例如:有业务逻辑做到功能复用

展示组件的复用性更强,灵巧组件则更专注于业务本身。

# 什么是高阶组件/高阶函数

如果一个函数可以接收另一个函数作为参数,且在执行后返回一个函数,这中函数就称为”高阶函数“

  • 优点:

    • 用于抽取公共逻辑
    • 链式调用
    • 渲染劫持
  • 缺点:

    • 流失静态函数:由于被包裹了一层,所以静态函数在最外层是无法获取的。如果希望外界能够使用,那么可以将静态函数复制出来,社区的解决方案使用 hoistNonReactStatics

    • refs 属性不能透传:使用 React.forwardRef 解决这个问题

# React 合成事件

优化是事件的操作。 例如有1000个 li 上绑定点击事件,只要在父标签 Ul 上绑定事件,利用事件冒泡的机制,找到当前点击的哪个 li。在触发相关操作。

  • React 给 document 挂上事件监听
  • DOM 事件触发后冒泡到 document
  • React 找到对应的组件,造出一个合成事件出来 但是:这里也有问题,在一个页面中,只能有一个 React版本,多个版本的话,事件就乱套了。在 React 17中解决了这个问题,事件委托不在挂在 document上,而是挂在 DOM 容器上,也就是 ReactDom.Render 所调用的节点上。

# 答题

React 组件应从设计与工程实践两个方向进行探讨。

从设计上而言,社区主流分类的方案是展示组件与灵巧组件。

展示组件内部没有状态管理,仅仅用于最简单的展示表达。展示组件中最基础的一类组件称作代理组件。代理组件常用于封装常用属性、减少重复代码。很经典的场景就是引入 Antd 的 Button 时,你再自己封一层。如果未来需要替换掉 Antd 或者需要在所有的 Button 上添加一个属性,都会非常方便。基于代理组件的思想还可以继续分类,分为样式组件与布局组件两种,分别是将样式与布局内聚在自己组件内部。

灵巧组件由于面向业务,其功能更为丰富,复杂性更高,复用度低于展示组件。最经典的灵巧组件是容器组件。在开发中,我们经常会将网络请求与事件处理放在容器组件中进行。容器组件也为组合其他组件预留了一个恰当的空间。还有一类灵巧组件是高阶组件。高阶组件被 React 官方称为 React 中复用组件逻辑的高级技术,它常用于抽取公共业务逻辑或者提供某些公用能力。常用的场景包括检查登录态,或者为埋点提供封装,减少样板代码量。高阶组件可以组合完成链式调用,如果基于装饰器使用,就更为方便了。高阶组件中还有一个经典用法就是反向劫持,通过重写渲染函数的方式实现某些功能,比如场景的页面加载圈等。但高阶组件也有两个缺陷,第一个是静态方法不能被外部直接调用,需要通过向上层组件复制的方式调用,社区有提供解决方案,使用 hoist-non-react-statics 可以解决;第二个是 refs 不能透传,使用 React.forwardRef API 可以解决。

从工程实践而言,通过文件夹划分的方式切分代码。我初步常用的分割方式是将页面单独建立一个目录,将复用性略高的 components 建立一个目录,在下面分别建立 basic、container 和 hoc 三类。这样可以保证无法复用的业务逻辑代码尽量留在 Page 中,而可以抽象复用的部分放入 components 中。其中 basic 文件夹放展示组件,由于展示组件本身与业务关联性较低,所以可以使用 Storybook 进行组件的开发管理,提升项目的工程化管理能力。