不数不知道,React已经有22个hook了

37,313 阅读5分钟

大家好,我卡颂。

5月30日刚好是React10周年纪念日。

我顺手拉了下React最新代码,这一看不要紧,居然已经有22个hook了。

其中:

  • react包导出了21个

  • react-dom包导出了1个(useFormStatus

本文会从React这些年发展脉络的角度,聊聊这些hook的作用。

欢迎加入人类高质量前端交流群,带飞

时代的更迭

截止当前,React的发展主要经历了3个时期:

  • CSR时期(客户端渲染时期)

  • 并发时期

  • RSC时期(服务端组件时期)

当前的22个hook也都是这3个时期的产物。

CSR时期

时间回到2013年,为了解决facebook日益复杂的交互,jordwalke开发了React。经过一段时间摸索,React逐渐形成一套满足CSR的开发模式。

这套开发模式从ClassComponent迁移到FunctionComponent后,便形成了最初的一批hook。这些hook都与CSR的开发模式相关。比如:

与状态的流转相关的:

  1. useState

  2. useReducer

  3. useContext

与处理副作用相关的:

  1. useEffect

  2. useLayoutEffect

与提高操作自由度相关的:

  1. useRef

与性能优化相关的:

  1. useMemo

  2. useCallback

与调试相关:

  1. useDebugValue

随着React持续迭代,又引入了几个hook,本质来说他们都是为了完善CSR的开发模式,对现有hook能力进行补充或约束:

  1. useImperativeHandle(控制useRef防止其失控)

  2. useEffectEvent(对useEffect能力的补充)

  3. useInsertionEffect(对useEffect场景的补充)

  4. useMemoCache(减少性能优化心智负担)

这里简单聊聊useMemoCache。长久以来,不管是ClassComponentshouldComponentUpdate,还是FC中2个性能优化相关hook,都存在比较重的心智负担,比如:

  • 开发者需要考虑是否需要性能优化

  • 开发者需要考虑何时使用useMemouseCallback

为了解决这个问题,在2021年的React Conf,黄玄带来了能够通过编译器生成等效于useMemo、useCallback代码的方案 —— React Forget

useMemoCache就是React内部为React Forget提供缓存支持的hook

所以这个hook是给编译器用的,而不是我们普通开发者。

并发时期

在13年诞生之初,React的作者jordwalke就指出 —— React未来会发展并发特性

这并不是什么高瞻远瞩的预言,React本身是个重运行时的框架,这意味着他的迭代方向需要围绕运行时展开。而并发特性是一种优秀的运行时性能优化策略。

随着并发特性落地,首先推出的是2个并发相关hook

  1. useTransition

  2. useDeferredValue

这2个hook的本质都是降低更新的优先级,更新意味着视图渲染,所以当更新拥有不同优先级后,这意味着视图渲染拥有不同优先级。

这就是并发更新的理论基础。

但是,并发更新的出现,打破了React沿袭多年的一次更新对应一次渲染的模式。

为了让现有的库兼容并发模式,推出了如下hook

  1. useMutableSource

  2. useSyncExternalStore

所以,上述2个hook主要是面向开源库作者。

RSC时期

RSC(服务端组件)是一个浩大的工程,他的实现不是一蹴而就的,这一点从新出的hook就能看出。

既然是服务端组件,那就涉及到组件在服务端渲染。那么,对于存在唯一标识(比如下面的id props)的组件,如何保证这个唯一标识在服务端与客户端一致呢?

<SomeCpn id={id}/>

如果组件仅在一端渲染,简单使用Math.random()就能获得唯一标识:

const id = Math.random();

<SomeCpn id={id}/>

但如果这段逻辑在服务端/客户端都运行一次,显然id就不唯一了。

为了生成在服务端/客户端唯一的id,有了:

  1. useId

在并发时期,由于引入了渲染优先级的概念,那势必存在一些由于优先级不足,而处于pending中的渲染。

如何展示渲染的pending状态呢?React引入了<Suspense>组件。

到了RSC时期,React团队发现,渲染的pending状态pending数据请求的pending状态不也是pending吗?

换言之,任何需要中间pending状态的流程,不都可以纳入<Suspense>的管理范围?

那该怎么标记一个流程可以被纳入<Suspense>的管理呢?于是有了:

  1. use

通过这个hook声明的流程中的pending状态都会被纳入<Suspense>的管理。

既然<Suspense>越来越重要,那我们是不是要针对他做些优化?既然<Suspense>可以在不同视图之间切换,那为他增加缓存显然是种不错的优化方式,于是有了:

  1. useCacheRefresh(用于建立<Suspense>缓存)

到这一步,RSC的基础设施算是搭好了,下一步该构建上层应用了。

在浏览器端,与RSC理念最契合的便是form标签,围绕form标签的action属性,React推出了如下hook

  1. useOptimistic

  2. useFormStatus

这2个hook都是为了优化表单提交这一场景(也可以说是RSC与客户端的交互场景)。

关于这2个hook,更详细的解释可以参考form 元素是 React 的未来一文

总结

如果说CSR时期的hook都是面向开发者直接使用的。那么并发时期最初的2个hookuseTransitionuseDeferredValue)已经鲜有开发者使用了,而后期类似useMutableSource这样的hook,普通开发者则根本用不到。

同样的,再往后的RSC时期的所有hook,普通开发者都用不到。他们都是为其他库、框架(比如Next.js)提供的。

这标志着React发展方向的不断变化:

  • 早期,定位是前端框架,主要为了解决facebook自身问题,顺便开源,受众是开发者

  • 中期,定位是底层UI库,受众是开源库作者

  • 当前,定位是web底层操作系统,受众是上层全栈框架