经历了v17的平缓过渡,React 3月29日正式发布了React v18版本。这个版本带来了一些十分重要的能力。但大家伙不必担心学不动,这个版本无破坏性更新,hooks 还在。以下是核心功能更新。
作为以上能力的外显,补充了一些新的 API 和hooks.同时,将一些旧的 API 标记为 Deprecated 或者 Limited.
为了保证新版本的渐进升级,React 谨慎在释放新的能力,为了支持新的并发渲染特性,React 引入了一个新的 Root API:
使用了 createRoot
之后,会默认在后台使用并发渲染,以提升性能。当然,如果我们继续使用旧的 Render API,React 会按v17的方式去工作。以下是所有特性的一览表:
为了更好地理解 React 18,我强烈建议你阅读官方给出的以下两篇 blog。
同时,如果你还有一些疑惑,在 React 仓库的discussion 区,有一次很有趣的讨论:如何我是五岁小孩,你会如何给我解释 v18 带来的新特性:
接下来,我会对这个版本的核心能力一一解读。
https://github.com/reactwg/react-18/discussions/4
Concurrent Render,作为这个版本引入的核心能力,到底做了什么事呢?事实上,在 v17 版本,React 就提出了一个实验性的模式:Concurrent Mode,它就是 Concurrent Render 的前身。首先需要明确的是,它并不是一个 feature,或者 API,而是类似于 v16 提出的 Fiber Reconciler,是对React 底层渲染模型的增强。
Concurrency 建立在 Fiber 的基础上。前情提要,我们来回顾一下 Fiber 做了什么。在 Fiber 之前,React 底层使用 stack reconciler 来更新 vDOM,这样的问题显而易见,任意一次 state 的变更,都会触发整颗 vDOM 树的更新,这是一个漫长的过程。Fiber 使用了 while-loop 的方式,来替代更新 vDOM 的更新过程,使用 while 循环,允许有一个寻找更新节点的钩子,来决定需要更新的部分,这也就是我们所说的分片能力,我们不必再等整个 vDOM 都更新。
但是,Fiber reconciler 的问题是:
为了解决以上的痛点,Concurrency 在v18版本作为核心能力出场了,在 Concurrency 的模式下,首先对更新的行为做了升级:
以上的三种特性,正是 Concurrent Render 主要做文章的能力。那作为并发特性的外显,React 18 提供了以下 feature:
可以称作批量更新,React 将多个状态更新,聚合到一次 render 中执行,以提升性能。在之前版本中,原生事件和 setTimeout 等行为中的多次更新都不会被合并。也就是说,每次 state 的变化,都会触发 re-render. 在新的版本中,如果你使用了新的 root API,以上的场景都会自动启动批量更新的能力。
before 18:
// Before React 18 only React events were batched
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will render twice, once for each state update (no batching)
}, 1000);
after 18:
// native event handlers or any other event are batched.
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}, 1000);
当然,如果你不想应用这份能力,可以使用 flushSync
选择停用。
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// React has updated the DOM by now
flushSync(() => {
setFlag(f => !f);
});
// React has updated the DOM by now
}
我们在上文提到,React 18 将 UI 的更新分为 Urgent Update 以及 Transition Update,前者的优先级更高,在之前版本,所有的更新都被当做前者处理。所以,为了平滑升级,新版本提供了一个新的 API,useTransiton
来允许开发者手动指定哪些更新是 Transition 的部分。例如一个搜索按钮之后的视图变化,我们可以认为属于过渡视图,用户的预期中也是允许等待的,那我们就可以使用新的 API 来指定这些更新,让他们为更高的优先级的更新任务让步。
作为本次新版本的另外一个重量级特性,Suspense
在未来的开发中很值得我们期待。
Suspense
是一种异步数据获取的机制,对 Concurrent Render 的支持以及引入服务端。
rfcs/0213-suspense-in-react-18.md at main · reactjs/rfcs
它的原理是将子组件的渲染优先级降低,如果一个 Promise 还没有被 resolve,就会渲染 fallback
。一旦 promise resolve,就触发渲染子组件的渲染。
这种异步数据的处理方式有很多优点:
Concurrency 和 Suspense 另一个大展身手的场景是 SSR.这个版本对 SSR 的处理模式做了大大的增强。
New Suspense SSR Architecture in React 18 · Discussion #37 · reactwg/react-18
在历史版本中,SSR 是针对整个 APP 的加载:
可以看出,在历史版本中,每一步必须完成针对整个 APP 的操作才能进入下一步。这样我们花费了大量的等待时间。
18版本优化的方式概而言之即是:**拆分。**具体的策略可以分为以下两个方面:
<script>
标签,这里的script 用于页面结构的内联:<div hidden id="comments"> <!-- Comments --> <p>First comment</p> <p>Second comment</p> </div> <script> // This implementation is slightly simplified document.getElementById('sections-spinner').replaceChildren( document.getElementById('comments') ); </script>Suspense
实现1. 允许尽早进行 hydration 操作,即便剩余的HTML和JS还没有被加载。一个页面可能包含很多模块,某模块还没有被返回,页面中可以渲染 Suspense 提供的 fallback,已经加载过来的模块可以及时被 hydrate.
2. 允许根据用户交互来改变 hydration 的优先级(Selective Hydration)。这里的意思是,我们的 hydrate 操作可以被中断,举个例子,如果一个按钮的结构已经被返回,但还没有被 hydrate,它在等待另外一个模块 hydrate 完成。但此时,如果用户点击了一下按钮,React 会把按钮的优先级提高,暂停另一个模块的 hydrate,优先对按钮模块进行 hydrate,以便于快速地响应用户的交互诉求。之后再接着之前没有完成的工作。
整体来说,新的 SSR 支持了组件级别的流式渲染,在 server 端进行了提早的返回,在 client 端尽早地进行 hydrate,哪怕只返回了部分页面结构。并且对用户请求交互的部分优先进行 hydrate.
新的 SSR 模式下的API 变化
renderToNodeStream
Deprecated → 使用 renderToPipeableStream
代替renderToReadableStream
renderToString
和 renderToStaticMarkup
被标记为 LimitedrenderToStaticNodeStream
render emailuseId
: 在客户端和服务端同时生成新的unique id,以避免不必要的更新[useSyncExternalStore](<https://github.com/reactwg/react-18/discussions/70>)
:允许外部状态管理器,强制立即同步更新,以支持并发读取。这个新的 API 推荐用于所有 React 外部状态管理库useInsertionEffect
:解决 CSS-in-JS 库在渲染中动态注入样式的性能问题。除非你已经构建了一个 CSS-in-JS 库,否则我们不希望你使用它。这个 Hook 执行时机在 DOM 生成之后,Layout Effect 执行之前startTransition
:用于过度视图的转换,为了兼容,手动触发useDeferredValue
新的 API 可以去官方文档深入了解。
React 未来会增加保留组件之前状态的能力,例如返回 Tab 页时保留之前的 Tab 浏览状态。为了检测是否是符合要求的组件写法,在18版本的严格模式的开发环境下,会模拟一个组件卸载再用保存的状态re-render的过程:
在以前,React 加载组件的逻辑为:
- `React mounts the component.`
`* Layout effects are created.`
`* Effect effects are created.`
在 React 18 严格模式的开发环境,React 会模拟卸载并重载组件:
`* React mounts the component.`
`* Layout effects are created.`
`* Effect effects are created.`
`* React simulates unmounting the component.`
`* Layout effects are destroyed.`
`* Effects are destroyed.`
`* React simulates mounting the component with the previous state.`
`* Layout effect setup code runs`
`* Effect setup code runs`
18 使用的一些现代浏览器能力,例如 microtasks 等能力不能在IE中很好polyfilled.
1. 如果promise执行得很快,它的结果由garbage-collected处理
2. 如果promise执行得很慢,它的结果并不会被生效
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。