大家好,我卡颂。
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的开发模式相关。比如:
与状态的流转相关的:
- useState
- useReducer
- useContext
与处理副作用相关的:
- useEffect
- useLayoutEffect
与提高操作自由度相关的:
- useRef
与性能优化相关的:
- useMemo
- useCallback
与调试相关:
- useDebugValue
随着React持续迭代,又引入了几个hook,本质来说他们都是为了完善CSR的开发模式,对现有hook能力进行补充或约束:
- useImperativeHandle(控制useRef防止其失控)
- useEffectEvent(对useEffect能力的补充)
- useInsertionEffect(对useEffect场景的补充)
- useMemoCache[1](减少性能优化心智负担)
这里简单聊聊useMemoCache。长久以来,不管是ClassComponent的shouldComponentUpdate,还是FC中2个性能优化相关hook,都存在比较重的心智负担,比如:
- 开发者需要考虑是否需要性能优化
- 开发者需要考虑何时使用useMemo、useCallback
为了解决这个问题,在2021年的React Conf,黄玄带来了「能够通过编译器生成等效于useMemo、useCallback代码」的方案 —— React Forget。
useMemoCache就是React内部为React Forget提供缓存支持的hook。
所以这个hook是给编译器用的,而不是我们普通开发者。
并发时期
在13年诞生之初,React的作者「jordwalke」就指出 —— React未来会发展「并发特性」。
这并不是什么高瞻远瞩的预言,React本身是个重运行时的框架,这意味着他的迭代方向需要围绕「运行时」展开。而「并发特性」是一种优秀的运行时性能优化策略。
随着并发特性落地,首先推出的是2个并发相关hook:
- useTransition
- useDeferredValue
这2个hook的本质都是降低更新的优先级,「更新」意味着「视图渲染」,所以当更新拥有不同优先级后,这意味着「视图渲染」拥有不同优先级。
这就是并发更新的理论基础。
但是,并发更新的出现,打破了React沿袭多年的「一次更新对应一次渲染」的模式。
为了让现有的库兼容并发模式,推出了如下hook:
- useMutableSource
- useSyncExternalStore
所以,上述2个hook主要是面向开源库作者。
RSC时期
RSC(服务端组件)是一个浩大的工程,他的实现不是一蹴而就的,这一点从新出的hook就能看出。
既然是服务端组件,那就涉及到组件在服务端渲染。那么,对于存在唯一标识(比如下面的id props)的组件,如何保证这个唯一标识在服务端与客户端一致呢?
<SomeCpn id={id}/>
如果组件仅在一端渲染,简单使用Math.random()就能获得唯一标识:
const id = Math.random();
<SomeCpn id={id}/>
但如果这段逻辑在服务端/客户端都运行一次,显然id就不唯一了。
为了生成在服务端/客户端唯一的id,有了:
- useId
在并发时期,由于引入了「渲染优先级」的概念,那势必存在一些由于优先级不足,而处于pending中的渲染。
如何展示「渲染的pending状态」呢?React引入了<Suspense>组件。
到了RSC时期,React团队发现,「渲染的pending状态」是pending,「数据请求的pending状态」不也是pending吗?
换言之,任何需要中间pending状态的流程,不都可以纳入<Suspense>的管理范围?
那该怎么标记一个流程可以被纳入<Suspense>的管理呢?于是有了:
- use
通过这个hook声明的流程中的pending状态都会被纳入<Suspense>的管理。
既然<Suspense>越来越重要,那我们是不是要针对他做些优化?既然<Suspense>可以在不同视图之间切换,那为他增加缓存显然是种不错的优化方式,于是有了:
- useCacheRefresh[2](用于建立<Suspense>缓存)
到这一步,RSC的基础设施算是搭好了,下一步该构建上层应用了。
在浏览器端,与RSC理念最契合的便是form标签,围绕form标签的action属性,React推出了如下hook:
- useOptimistic
- useFormStatus
这2个hook都是为了优化「表单提交」这一场景(也可以说是RSC与客户端的交互场景)。
关于这2个hook,更详细的解释可以参考form 元素是 React 的未来一文。
总结
如果说CSR时期的hook都是面向开发者直接使用的。那么并发时期最初的2个hook(useTransition、useDeferredValue)已经鲜有开发者使用了,而后期类似useMutableSource这样的hook,普通开发者则根本用不到。
同样的,再往后的RSC时期的所有hook,普通开发者都用不到。他们都是为其他库、框架(比如Next.js)提供的。
这标志着React发展方向的不断变化:
- 早期,定位是前端框架,主要为了解决facebook自身问题,顺便开源,受众是开发者。
- 中期,定位是底层UI库,受众是开源库作者。
- 当前,定位是web底层操作系统,受众是上层全栈框架。
参考资料
[1]useMemoCache:https://github.com/facebook/react/pull/25123。
[2]useCacheRefresh:https://github.com/reactwg/react-18/discussions/25。