React 19 RC:迟来两年的变革
编辑React 19 RC:迟来两年的变革
迟来的 React 19
2024 年 4 月 25 日,React 19 RC 发布,这一时间点距离 React 18 发布已经过去了两年。
整体而言,React 19 带来了诸多性能上和开发体验上的提升,但随之而来的思路与设计上的变化能否顺利推行,仍需观望。
React Compiler:性能倍增
现状:React 的性能优化短板
近几年,诸如 Solid.js
等前端框架拥抱基于 Signal 的细粒度更新,从而获得了更高的性能体验。相比之下,React 自身基于 Fiber 的 Diff 更新方式造成了大量的冗余 re-render,产生了很高的性能损耗。
尽管 React 提供了 useMemo
和 useCallback
这类 Hook 对代码进行优化,以减少 re-render 并优化性能,但其应用有着诸多限制,甚至如果滥用,反而会造成性能降低。
针对以上情况,React 19 推出了 React Compiler,可以自动优化项目代码,使得项目即使在不使用 memo hooks
的情况下,仍然可以最大可能地实现按需 re-render。
React Compiler
React Compiler 并不是新面孔,它以 React Forget 的名字在 React Conf 2021 出现,并在三年之后正式迎来了开源。
从 React 的更新机制说起
React 的理念是:Component = function(state)
,基于这个理念,一切组件以状态为基础,即 React 中的任一组件发生了 状态变更,React 都会从组件树的根节点向下递归对比,判断哪些节点发生变化,并更新节点。
这种更新机制有一个问题:只要在比较中, props、state、context
有任意一个不同,该节点就会被更新,因而造成了大量冗余的 re-render。
在大多数项目中,组件的状态并不会频繁更新,因而性能问题体现得并不明显;即使有这个需求,也可以使用 useMemo
和 useCallback
来暴力缓存节点,但这两个 hook 给开发者造成了较重的心智负担。
早有布局的 StrictMode
React Compiler 的引入已经早有布局。彼时的 React 18 中引入了严格模式(Strict Mode),启用之后可以检测项目中的 React Hook 使用是否规范,以及是否产生了意外的副作用等等,而这其实是在为 React Compiler 的引入铺路。
通过 React 严格模式检查的代码,大多数都符合 React Compiler 的优化规则,从而可以实现自动优化。
这是 React Compiler 的一个巨大优点:无侵入性——引入 Compiler 插件后,几乎不需要更改任何现有的项目代码,即可实现自动优化。
React Compiler 的优化原理
React 使用 Fiber 结构存储组件树,其本质是一个链表。在编译过程中,Compiler 会把该组件所有的可能的渲染结果都存储在一个缓存数组内,如果存储在数组中的结果可以复用,就会直接读取并使用。
由此,Compiler 实现了从 Fiber 结构(链表)到简单数组的转换,由此显著提升了执行效率。
为了实现以上内容,React 为 Compiler 引入了一个新 Hook:useMemoCache
,这个 hook 用于创建一个缓存数组 $
。
例如,对于一个不依赖外部变量的 React 元素:
<div id="test">测试</div>
经过 Compiler 优化后,变为:
let t1
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <div id="test">测试</div>;
$[1] = t1;
} else {
t1 = $[1];
}
该组件函数被执行时,都会经过 Symbol.for
函数来确定是否已被初始化过,若已经初始化过(被存入 $
数组中),则不进行对象创建,直接取出数组中的缓存并使用。
相比较于创建一个 React 元素,if 判断的成本是非常低的,因此经过 Compiler 优化后显著地了提高效率。
React Compiler 的细粒度更新
前面提到,React 使用 Diff 找出需要更新的节点,但 Compiler 编译似乎并未对这一更新方式进行修正,仅仅是更改了组件的缓存方式。
是的,即使引入了 Compiler,React 仍然没有像 Vue 等框架进行依赖收集。
由于 Compiler 编译时会缓存组件函数所有可能的结果,会几乎缓存所有函数和元素,所以产生了极高的缓存命中率,基于此,Compiler 实现了无依赖收集的,精确到元素级别的细粒度更新。
对 React Compiler 的总结
经过三年磨砺,React Compiler 的引入以侵入度极低的方式,解决了 React 目前存在的性能问题,无疑能够确保 React 在前端框架的领先地位。
由于相比使用 memo hooks
进行手动优化,其上手成本要低得多,因此一定会被社区快速跟进。可以说,Compiler 为沉寂了两年的 React 生态圈带来了新的活力,以及可预见的进一步繁荣。
前途一片光明啊。
尽力避免 useEffect:开发体验的巨大优化
useEffect:不得不用,不得不品
useEffect 是 React 处理异步时不得不使用的 Hook,但因其处理依赖项难度高,极易造成闭包问题,React 官方教程曾经拿出极大的篇幅讲解 useEffect
使用的规则。
幸运的是,React 19 的大部分更新,几乎都在围绕着如何减少使用 useEffect
这个 Hook 而进行的。
但是,如果不用 useEffect
,又该如何处理异步问题?
UI 交互的彻底革新:新的异步解决方案
React 18 中引入了 Suspense
、ErrorBondary
的概念,但由于设计不成熟,这两个专门用于处理异步更新的方法并未普及开来,甚至 React 的官方文档也未深入介绍。
在 React 19 中,引入了新概念 Action
以及几个新 Hook,并对 Suspense
和 ErrorBondary
进行了重新设计。
Action:新的异步解决方案
React 19 通过改进的 useTransition
、useActionState
、useFormStatus
等Hook,实现了对异步函数的自动状态管理、错误处理以及乐观更新等。
通过使用这些 Hook,可以不必再使用 useEffect
手动更新状态,一切变化由 Action 异步函数的状态决定。
function ChangeName({ name, setName }) {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const error = await updateName(formData.get("name"));
if (error) {
return error;
}
redirect("/path");
return null;
},
null,
);
return (
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>Update</button>
{error && <p>{error}</p>}
</form>
);
}
use Hook:优雅处理 Promise
React 19 中,新增了 use
Hook,可以通过该 Hook 以直观的方式处理 Promise 数据。
function buildComments({getCommentPromise}){
const data = use(getCommentPromise);
return data.map((data, i) => <p key={i}>{data}</p>);
}
直到 use
使用的 Promise 进入 resolved 状态,才会执行使用该 Promise 数据的函数,因此无需再手动处理 Promise 的状态。
通过与 Suspense
结合使用,可以写出相比使用 useEffect + setState
的方式,要简洁易懂得多的代码。
改进的 Suspense:彻底解决竞态问题
竞态问题确是前端开发老生常谈的问题之一,React 19 在框架的层面给出了一个解决方案。
通过将 use Hook 与 Suspense 结合使用,React 会严格限制异步函数的请求顺序,即使多次触发,也保证上一个请求请求成功之后,下一个请求才开始发生。
在这个过程中,对于多次请求产生的重复数据,React 会将之视为无效数据,并只在最后一个请求完成时触发状态更新。
总结
两年之后,终于等来了睽违已久的 React 大版本更新。
可以说,这次更新解决了 React 目前在性能上以及开发体验上的多数痛点,是比肩 React 16 引入 React Hook 和函数式组件的一次意义非凡的更新。
根据 npm 数据,目前社区对 React Compiler 有着相当大的热情,对 Compiler 的适配正在快速跟进,普及并成熟指日可待。
前途一片光明啊(再次)。
- 0
- 0
-
分享