常用 React 设计模式详解(上)

338 阅读13分钟

1. 前言

嘿,我们上次聊过React和设计模式,这次我们继续聊聊在React中如何运用设计模式来创建和复用组件,详细讨论下创建型模式和结构性模式。创建型模式,有容器组件和展示组件这对好搭档,还有 Hooks 模式这个新宠儿。结构型模式,组合模式、Render Props 模式和高阶组件模式,它们都是咱们的好帮手哦!

2. 容器组件与展示组件模式

2.1 例子

咱们从一个简单的时钟例子入手吧,先说说这个例子有啥问题,然后再来优化它。这个时钟组件,它会接收一个Date对象作为属性,然后显示实时的时间。我们先把组件的状态初始化一下,存下当前的时间。接着,我们用setInterval来每秒更新一下状态。这里有个_updateTime函数,它是用来把time对象设置成当前时间加一秒的。还有个_formatTime函数,它是用来提取时分秒,并确保它们都是两位数的形式。

8DB6FD4B-FE48-45BA-BAFC-9A0F8D3E5E21.png

在咱们开始改造组件之前,先一块儿瞅瞅容器组件和展示组件模式的定义呗。

2.2 定义

  • 定义:通过将复杂组件拆分为容器型组件和展示型组件来增加组件的可复用性。
  • 职责分离
    • 容器组件负责获取数据、管理状态和处理逻辑。
    • 展示组件只负责接收数据并渲染,不关心数据的来源或如何处理,有限的自身纯UI状态。
  • 协作方式
    • 容器组件通过 props 传入数据和回调函数传递给展示组件。
    • 展示组件的用户交互(如点击事件)会触发回调函数执行容器组件相应的逻辑

2.3 改造时钟例子

现在,我们来改造时钟例子,拆分为 ClockContainer 和 Clock 组件。ClockContainer 负责获取时间对象,并抽离对应的数据结构,然后传给 Clock 展示组件进行展示。

B789581D-56B5-4BD3-AED9-7CA7320B5254.png

2.4 小结

容器组件与展示组件符合创建型设计模式,职责分明,复用组件,避免重复建设。

  • 缺点
    • 增加不同组件直接代码跳转成本,应根据实际情况合理使用。
  • 优点
    • 分离关注点:将应用逻辑和 UI 渲染分离,简化代码维护和测试。
    • 代码复用:展示组件可以复用于不同的容器组件中,提高代码的复用性。
    • 提高可读性:使组件的职责更加明确,代码结构更清晰易懂。

3. Hooks模式

3.1 class 组件与函数组件例子对比

接下来咱们聊聊Hooks模式吧,Hooks的引入其实背后是有故事的,咱们可以瞅瞅这个例子。

609CEEBE-7F07-45B5-AE98-022CA3C3FDED.png

这个class组件可厉害了,它做了几件事:监听用户的输入,然后自动更新到docment文档上;还能监听窗口大小变化,并打印出width值;最后还能渲染主题状态。不过,这个组件有个小问题,就是它的业务逻辑分散在好多不同的生命周期里,比如监听窗口缩放的逻辑和更新document title的逻辑,这些都不能在其他地方复用,有点不方便。

而且,用户输入函数还得绑定this,有点繁琐。但是,如果我们用函数组件和hooks来构建的话,这些问题就都能解决了。你看第二张图,我们可以用useState和useEffect来封装自己的hook,这样逻辑就都放在一个地方了,不需要再绑定this,代码看起来也更直观简洁了。

总结:

  • class 组件
    • 业务逻辑分散(不同生命周期)
    • “有状态的逻辑”复用困难,HOC 嵌套地狱、命名冲突。
    • 组件巨大、this 绑定问题
  • 函数组件
    • hooks 存储数据,不用知道怎么存、存在哪、用就行了(数据可同步、异步过来)

3.2 Hook 模式定义

定义:组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码“钩”进来。React Hooks 就是那些钩子。

AB361380-566B-445F-BBB1-7EF41D81F330.png

除了内置的 Hooks 外,我们还可以自定义 Hooks。

什么样的场景考虑自定义 hook呢?

3.3 自定义 Hooks 的创建和使用

自定义 hook 是基于 react 内置 hook 上的扩展,根据业务定制 hook,复用逻辑。

2A94B898-18E8-43E0-9371-94BDE1A57783.png

看看这个代码截图,咱们把 cookie 和 hook 结合起来玩了个新花样。初始化state的时候,直接从cookie里拿状态,要是cookie变了,数据就同步到state里。这样一来,其他组件用这个hook就能直接享受持久化的便利啦!

你可以多看看 hook工具库,比如react-use、ahook,它们提供了数据请求、DOM操作、定时器等各种场景的解决方案。然后你可以根据自己的业务需求进行定制开发哦!

3.4 Hooks 使用问题

有个挺有意思的hook问题,搞懂它对我们理解Hooks的工作机制超有帮助的。你知道为啥React不建议我们在条件语句里用hooks吗?

3FA6E815-B43F-497B-8746-3E0F3B3682D8.png

Hooks 就是那种在 React 里头有特别机制的函数,它们用来管理组件的状态和副作用。咱们来看看 Hooks 是怎么工作的吧。React 用闭包来存状态,然后用 hook 数组来隔开每次 hook 的执行。每次组件渲染的时候,React 都会重新跑一遍函数组件,然后从头到尾按顺序调用 Hooks。

整个代码被分成了四个部分,我们初始化了两个变量,hook 和 currentHook,这两个变量在组件渲染、useEffect 和 useState 的时候都会用到。

65E58ED8-3EE9-4BD1-84BB-DF792D9695D1.png

解答:

  • 每次React渲染组件的时候,它都会重新跑一遍函数组件,并且把这些Hooks都登记到一个列表里,按照它们被调用的顺序来管理,还会用闭包来保存状态。这就是为什么我们不能在条件语句或者循环里面调用Hooks,因为这样会打乱它们被调用的顺序,导致我们可能拿不到正确的状态。

  • 闭包陷阱这事儿挺有意思的。比如说,如果你在effect函数的回调里用了A和B两个状态,但是呢,你只把A加到了依赖数组里。这样一来,当回调执行的时候,它就只能记住B第一次的状态了,B就永远都是旧的了。

3.5 小结

Hooks 模式真的很实用,它既可以作为创建型设计模式,也能作为结构型设计模式。比如,useState 就可以用来创建状态对象和管理状态函数,而自定义 hooks 则跟装饰器模式有点像,用起来特别方便。

  • 缺点
    • 需要小心闭包问题
  • 优点
    • 无需类组件,组件状态逻辑复用方便,自定义 hook 还可以互相组合

4. 组合模式

4.1 组合模式的使用场景有哪些?

在React开发中,咱们经常得把好几个组件拼一块儿,整出更复杂的UI界面来。像Form和Form.Item这种UI组件,前端小伙伴肯定都常用。Form就像是装表单的盒子,Form.Item就是里面装每个输入框的小格子。咱们可以把好几个Form.Item放到Form里,拼成一个完整的表单。

Tab和Tab.Panel也是这种组合玩法的典型代表。Tab就像是管家的角色,负责管着好几个Tab.Panel,让用户能在不同的面板之间来回切换。每个Tab.Panel都能独立存在,但有了Tab,它们就能被统一管理起来。

还有Row和Col,这俩也是咱们构建响应式布局的好帮手。Row就像是放行的盒子,Col就像是列的小格子,它们俩一组合,就能整出各种灵活的布局来。

FD76A786-045E-4929-B8E0-E981561D7DF0.png

4.2 组合模式定义和原理

定义:通过集成多个具有明确、单一职责的小型基础组件,构建出功能完备的大型组件。这一过程涉及对组件部分与整体的统一管理和协调,确保父组件与子组件之间的有效组合与协作。

应用场景:适合一些容器组件场景,通过外层组件包裹内层组件,外层组件可以获取内层组件的 props 状态,控制渲染。

原理:组合模式就是让我们可以通过外层组件来拿到内层组件的孩子们(children),然后我们可以使用 cloneElement 来给它们传新的状态,或者控制它们的渲染,甚至通过 displayName 这个静态属性来检查它们是哪个组件。

76C3772C-C9D5-4A08-A74B-B0E1A45B6242.png

咱们来看看这个整体的流程图吧。外层组件可以把props和回调传给内层组件,还能控制内层组件的渲染哦。

E667FCE1-CAF5-41A3-9AF2-77F250822BC3.png

4.3 总结

在软件开发中,传统的组合模式通过定义抽象类来统一处理单个对象和组合对象,这种设计允许父子元素递归嵌套,常见于树形数据结构等场景。相比之下,React 框架则摒弃了传统的继承机制,转而采用更为灵活的组件组合方式,侧重于通过UI组件的组合来表达部分与整体的关系,从而能够构建出更为复杂且动态的UI 结构。

  • 缺点:

    • 过度抽象:可能导致组件层次过多,增加系统复杂性和理解成本
  • 优点

    • 高复用性和灵活性:通过组合组件实现功能,不影响现有代码结构。
    • 易于维护:每个组件专注于单一功能,使得代码更易理解、维护和测试

5. Render Props模式

5.1 模式定义和使用

定义:类似于组合模式,但不同之处在于,children 可以通过函数的形式作为 prop 传入,或者直接通过 prop 的 render 属性进行传递。这种处理方式提供了更灵活的数据传递方式。

数据:函数的参数,由容器提供,将容器的状态提供到当前外层组件

首先,咱们来看看第一个使用例子,就是render prop的例子,简单点的,比如一个todoList怎么用。

7714EEF5-2CD9-4D1F-BEDD-8C3910D49A83.png

然后,第二个例子是通过children来用的,就是Context上下文消费的使用。

D968681A-8158-48C0-A1B2-2229AA9AD48D.png

:为啥要用这种设计模式呢?它特别适合容器包装,方便获取状态,使用起来也很灵活。还能解决数据源相同但渲染方式不同的复用问题,挺实用的。

5.2 实现原理

咱们来看看这个实现的原理吧,咱们不在组件里面直接搞定渲染的逻辑,而是把这个活儿交给一个传进来的函数。这样,组件自己就只管数据或者状态了,至于UI怎么渲染,那就交给外面去决定啦。

45ED246C-551F-4197-9F1B-05FA1975C338.png

5.2 实现一个带 loading 效果的容器组件例子

好啦,咱们来动手实践一下,实现一个带有加载效果的容器组件吧。这个组件可以装其他组件,并且还能通过传递 setLoading 来让外部控制它。这样,我们就可以在外部使用 Container 的 setLoading 来做我们想要的自定义处理了。

FFA308CF-2DAF-474F-8216-884E9A1531AA.png

5.3 小结

Render Props 就像是传统的策略模式一样,策略模式是把不同的算法封装起来,而 Render Props 则是把数据逻辑封装到容器组件里,让我们可以根据需要选择不同的渲染UI组件,这样开发起来就更方便了。

  • 缺点
    • 过度使用,可能会出现嵌套地狱
  • 优点
    • 非常灵活,很方便获取容器组件的状态和回调,进行复杂渲染。

6. 高阶组件模式

6.1 什么是高阶组件模式?

咱们来聊聊高阶组件模式吧,你们知道这是啥吗?我给你们举两个例子哈,第一个就是React Redux里共享state和dispatch函数的用法,第二个就是基础组件怎么利用React Router的location值。

3C7E8CB3-1375-4949-8D6F-C2EEF1EA975A.png

6.2 定义和作用

定义:一种调用函数,接受原始组件并返回原始组件的增强/封装版本。

作用:实现逻辑复用和组件增强,在不修改原始组件的情况下,增强或扩展组件的功能。

咱们来看看下面的例子哈,一个超简单的HOC抽象,还有一个需要传参数的HOC例子,其实就是前面react connect的一个抽象啦。

F630B182-7369-43C7-810D-426807C1B125.png

为啥要用高阶组件呢?这样可以在不改动原始组件的情况下,给它加点新功能或者扩展一下。而且,这个功能任何组件都能用,多方便啊!

6.3 实现原理

HOC 实现方式有两种:

  • 属性代理
  • 反向继承

6.3.1 属性代理实现方式

原理:用代理组件包裹原始组件,在代理组件上,做一些对原始组件的代理操作。

5C56F2A5-0641-4485-AC60-52DA365FACDF.png

  • 缺点
    • 无法直接获取业务组件的状态,需要 ref 获取组件实例
    • 无法直接继承静态属性
  • 优点
    • 与业务组件零耦合,只负责子组件渲染和传递 props
    • 适用于 class 和 function 组件

6.3.2 反向继承实现方式

原理:包装组件继承了原始组件本身。

774CADBD-0E38-484F-BF6F-3D9597144F21.png

  • 缺点
    • 函数组件无法使用
    • 和被包装的组件强耦合,需要知道被包装组件的内部状态。多个 hoc 嵌套容易覆盖生命周期
  • 优点
    • 方便获取组件状态,比如 state、props、生命周期、绑定事件等
    • 可以继承静态处理

6.4 小结

高阶组件真的是一种很棒的方法来增强和组合React元素,它跟传统的装饰器模式有点像,就是给组件穿上新衣服,让它变得更强大。

  • 缺点
    • 嵌套地狱,重复 HOC 属性覆盖,增加复杂度和理解成本。
  • 优点
    • 复用逻辑,根据业务定制。
    • 强化 props,比如 react-router  的  withRouter。
    • 赋能组件:额外的生命周期、额外的事件
    • 控制渲染,条件渲染、节流渲染、懒渲染(HOC + React.lazy + Suspense)

总结

咱们来简单总结一下哈,这篇文章主要就是讲了React设计模式里的创建型和结构型两种模式,挺有意思的。

  • 创建型模式:容器和展示组件模式,把逻辑和UI分开,让代码更灵活可复用。Hooks模式,现在React的标配啦!
  • 结构型模式:组合模式,帮你搭建复杂的UI结构。高阶组件(HOC),给组件加点料,减少重复代码。Render Props模式,渲染逻辑随你控制,但别搞成嵌套迷宫哦!

相关文章