React 18 新特性

兼容性

V18 已不再支持 IE11,计划时间是 2022 年 6 月 15 日, 因为用到的一些现代浏览器新特性如 micro-tasks,在 IE 中无法充分 polyfill .

并发

V18 版本在 V17 的基础上又做了一些调整。在过去的 V17 版本中,传统模式和并发模式是共存的, 通过 createRoot API 就可以启用并发模式。React 为向并发模式迁移的最初策略是设计三种模式。

  • Legacy 模式: V17 中默认使用的模式。默认开启严格模式。默认同步更新。Legacy Suspense semantics。
  • Blocking 模式: Legacy 和 Concurrent 混合模式。默认开启严格模式。默认同步更新。开放一些新特性。
  • Concurrent 模式:V18 使用的模式。默认开启严格模式。默认并发更新。开发所有的新特性。

React 最初的计划是用户可以从 Legacy 切换到 Blocking 模式,并不需要修改任何语法,配合严格模式(StrictMode)修改其中的报错,当所有错误被解决之后,可以直接切换到的 Concurrent 模式。

但在实际的场景 React 思考了以下几个问题:

  • 项目中会有成百上千个文件,开启严格模式,会有大量的错误信息,虽然不影响程序运行,但是会干扰开发并且不能快速的一次性解决。
  • 启用并发模式的好处不言而喻,可能会在未来默认启用。提供 startTransition Suspense 等 API 在内部实现并发特性。用户可以增量的选择性的使用这些 API 从而获得并发的特性。
  • 如果按照上述的思路,那么 Concurrent 模式和 Blocking 模式的唯一区别就只有是否提示错误信息。那么如果默认不启用并发模式,就可以不开启严格模式,用于提示错误信息。

基于上面的思考,V18 的策略是不会默认启用并发的特性,即使用 createRoot 启用并发模式并不能体验并发的特性,如果想体验并发的特性,需要使用例如 startTransition 等支持并发特性的 API. 所以官方描述为 没有并发模式,只有并发特性

API

  • startTransition

这个 API 可以防止渲染任务立即执行,允许将应用程序中的某些更新标记为非紧急更新,因此它们会暂停,同时优先考虑更紧急的更新。这可以在一个复杂的更新中相应用户输入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function aaaaaaaaaaaaaaa() {
for (let i = 0; i < 100000000; i += 1) {
const a = 1;
}
}
const [num, setNum] = useState<string>("");
const [list, setList] = useState<any[]>([]);
useEffect(() => {
setList(new Array(20000).fill(null));
setTimeout(() => {
const event = document.createEvent("MouseEvents");
event.initEvent("click", true, true);
document.getElementById("button")!.dispatchEvent(event);
}, 500);
}, []);
return (
<>
<button
id="button"
type="button"
onClick={() => {
setNum(() => {
aaaaaaaaaaaaaaa();
return "123";
});
}}
>
点击
</button>
<p>{num}</p>
{list.map((_, i) => (
<div key={Math.random()}>{i}</div>
))}
</>
);

在没有使用并发特性的时候,列表的渲染是一个同步任务,不会相应模拟的用户事件

当开启了并发特性之后,会被拆分成小任务异步执行

1
2
3
startTransition(() => {
setList(new Array(20000).fill(null));
});

  • useDeferredValue

会创建一个数据的副本,如果当前更新是一个紧急更新,useDeferredValue 会返回之前的状态,从而优先响应紧急更新。当紧急更新渲染完成后,才会去执行的当前更新。底层实现与 useDeferredValue 类似。

使用 useDeferredValue 也可以实现相同的效果

1
2
const [list, setList] = useState<any[]>([]);
const dList = useDeferredValue(list);
  • useId

生成一个唯一 ID, 在服务端与客户端生成的相同,防止 ID 不匹配

  • useSyncExternalStore

useSyncExternalStore  是一个新的 api,经历了一次修改,由  useMutableSource  改变而来,主要用来解决外部数据撕裂问题。

useSyncExternalStore 能够通过强制同步更新数据让 React 组件在 CM 下安全地有效地读取外接数据源。 在 Concurrent Mode 下,React 一次渲染会分片执行(以 fiber 为单位),中间可能穿插优先级更高的更新。假如在高优先级的更新中改变了公共数据(比如 redux 中的数据),那之前低优先的渲染必须要重新开始执行,否则就会出现前后状态不一致的情况。

useSyncExternalStore 一般是三方状态管理库使用,我们在日常业务中不需要关注。因为 React 自身的 useState 已经原生的解决的并发特性下的 tear(撕裂)问题。useSyncExternalStore 主要对于框架开发者,比如 redux,它在控制状态时可能并非直接使用的 React 的 state,而是自己在外部维护了一个 store 对象,用发布订阅模式实现了数据更新,脱离了 React 的管理,也就无法依靠 React 自动解决撕裂问题。因此 React 对外提供了这样一个 API。

目前 React-Redux 8.0 已经基于 useSyncExternalStore 实现。

  • useInsertionEffect

这个 Hooks 只建议  css-in-js 库来使用。 这个 Hooks 执行时机在 DOM 生成之后,useLayoutEffect 之前,它的工作原理大致和  useLayoutEffect  相同,只是此时无法访问  DOM  节点的引用,一般用于提前注入  <style>  脚本。

  • Suspense

官方对 空的 fallback 属性的处理方式做了改变:不再跳过 缺失值 或 值为 null 的 fallback 的 Suspense。如果没有指定 fallback 将会把 fallback 呈现为 null。

批处理

在 18 之前,只有在 react 事件处理函数中,才会自动执行批处理,其它情况会多次更新

在 18 之后,任何情况都会自动执行批处理,多次更新始终合并为一次

如果想要跳出批处理使用 flushSync

关于卸载组件时的更新状态警告

有的时候会遇到如下的错误

这个错误表示:无法对未挂载(已卸载)的组件执行状态更新。这是一个无效操作,并且表明我们的代码中存在内存泄漏。

实际上,这个错误并不多见,在以往的版本中,这个警告被广泛误解,并且有些误导。

这个错误的初衷,原本旨在针对一些特殊场景,譬如 你在 useEffect 里面设置了定时器,或者订阅了某个事件,从而在组件内部产生了副作用,而且忘记 return 一个函数清除副作用,则会发生内存泄漏…… 之类的场景

但是在实际开发中,更多的场景是,我们在 useEffect 里面发送了一个异步请求,在异步函数还没有被 resolve 或者被 reject 的时候,我们就卸载了组件。 在这种场景中,警告同样会触发。但是,在这种情况下,组件内部并没有内存泄漏,因为这个异步函数已经被垃圾回收了,此时,警告具有误导性。

综上所述原因,在 React 18 中,官方删除了这个报错。

返回值类型

在 React 18 中,不再检查因返回 undefined 而导致崩溃。既能返回 null,也能返回 undefined。但需要修改相应的 dts 文件。

Strict Mode

严格模式会打印两次日志,可以在 React DevTools 中关闭。

在 React 17 中,取消了其中一次渲染的控制台日志,以便让日志更容易阅读。

在 React 18 中,官方取消了这个限制。如果你安装了 React DevTools,第二次渲染的日志信息将显示为灰色,以柔和的方式显式在控制台。

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2025 SunZhiqi

此时无声胜有声!

支付宝
微信