在 2021 年 6 月 8 号,React 公布了 v18 版的正式发布计划,并正式发布了 alpha 版。经过整整两年的正式发布前预备,在 2022 年 3 月 29 日,React 18 测试版终于和大家碰面了。
React 18 如果是前段时间一两年的两个备受瞩目版,React 非官方对它抱有了众望。要不然也不能将 React 17 作为两个过渡阶段版,也不能光正式发布预备工作就做了两年。
在过去两年,他们早已多多少少了解到一些 React 18 的新功能。这首诗就要通过丰富的实例,向我们系统的如是说 React 18 带来的改变。当然责任编辑带入了很数个人认知,但如不对,Professionel尖萼。
Concurrent Mode
Concurrent Mode(以下全称 CM)译者叫mammalian商业模式,这个基本概念我早已听了好一两年了,并且曾一度非常忧虑
React 非官方憋了好一两年的大招,会不能是两个破坏力不相容的DT大版?就像 VUE v3 和 v2。原有的自然生态呢都得跟著大版升级?比如说 ant design,ahooks 等。随着对 CM 的了解,我发现它其实是家畜无毒的。
CM 这类并不是两个功能,而是两个下层设计,它使 React 能够与此同时预备数个版的 UI。
在从前,React 在状况更改后,会早已开始预备交互式 DOM,然后图形真实世界 DOM,整个业务流程是以太网的。除非早已开始促发预览,只能等业务流程完全结束,期间是无法受阻的。
在 CM 商业模式下,React 在继续执行过程中,每继续执行两个 Fiber,单厢看看呢更高错误率的预览,如果有,则现阶段低错误率的的预览会被中止,待高错误率各项任务继续执行完之后,再继续继续执行或重新继续执行。
CM 商业模式有点儿类似于计算机系统的多各项任务处理,CPU在与此同时进行的插件之间加速转换,或许 React 如果更名叫 ReactOS 了。
这里举个范例:他们正在看影片,这时扩音器响了,他们要去进门拿外卖。 在 React 18 从前,除非他们早已开始看影片,就不能被终止,必须等影片看完之后,才会去进门。 而在 React 18 CM 商业模式之后,他们就可以中止影片,等进门拿完外卖之后,再重新继续看影片。
不过对于普通开发者来说,他们一般是不能感知到 CM 的存在的,在升级到 React 18 之后,他们的项目不能有任何变化。
er rendering(流式服务端图形), 等等。
React 18 的大部分功能都是基于 CM 架构实现出来的,并且这这是两个早已开始,未来会有更多基于 CM 实现的高级能力。
startTransition
他们如果要主动发挥 CM 的优势,那就离不开 startTransition。
React 的状况预览可以分为两类:
紧急预览(Urgent updates):比如说打字、点击、拖动等,需要立即响应的行为,如果不立即响应会给人很卡,或者出问题了的感觉过渡阶段预览(Transition updates):将 UI 从两个视图过渡阶段到另两个视图。不需要即时响应,有些延迟是可以接受的。我从前会认为,CM 商业模式会自动帮他们区分不同错误率的预览,一键无忧享受。很遗憾的是,CM 只是提供了可受阻的能力,默认情况下,所有的预览都是紧急预览。
这是因为 React 并不能自动识别哪些预览是错误率更高的。
比如说以上实例,用户的键盘输入操作后,setInputValue会立即预览用户的输入到界面上,是紧急预览。而setSearchQuery是根据用户输入,查询相应的内容,是非紧急的。
但是 React 确实没有能力自动识别。所以它提供了 startTransition让他们手动指定哪些预览是紧急的,哪些是非紧急的。
如上代码,他们通过 startTransition来标记两个非紧急预览,让该状况促发的更改变成低错误率的。
光用文字描述我们可能没有体验,接下来他们通过两个实例来认识下可受阻图形对性能的爆炸提升。
实例页面:https://react-fractals-git-react-18-swizec.vercel.app/
如下图,他们需要画两个毕达哥拉斯树,通过一个 Slider 来控制树的倾斜。
那他们的代码会很简单,如下所示,他们只需要两个 treeLeanstate 来管理状况。
在每次 Slider 拖动后,React 继续执行业务流程大致如下:
预览 treeLean图形 input,填充新的 value重新图形树组件 Pythagoras每一次用户拖动 Slider,单厢同步继续执行上述三步。但当树的节点足够多的时候,Pythagoras 图形一次就非常慢,就会导致 Slider 的 value 回填变慢,用户感觉到严重的卡顿。如下图。
当数的节点足够大时,早已卡到爆炸了。在 React 18 从前,我们是没有什么好的办法来解决这个问题的。但基于 React 18 CM 的可受阻图形机制,他们可以将树的预览图形标记为低错误率的,就不能感觉到卡顿了。
Kapture 2022-04-10 at 21.16.29.gif以上代码,他们通过 startTransition 标记了非紧急预览,让树的预览变成低错误率的,可以被随时中止,保证了高错误率的 Slider 的体验。
此时预览业务流程变为了
input 预览 treeLeanInput 状况更改预备新的 DOM图形 DOM 树预览(这一次预览是低错误率的,随时可以被中止) treeLean 状况更改预备新的 DOM图形 DOMReact 会在高错误率预览图形完成之后,才会启动低错误率预览图形,并且低优先级图形随时可被其它高错误率预览受阻。
当然,在低优先状况等待预览过程中,如果能有两个 Loading 状况,那就更好了。React 18 提供了 useTransition来跟踪 transition 状况。
自动批处理 Automatic Batching
批处理是指 React 将数个状况预览,聚合到一次 render 中继续执行,以提升性能。比如说
在 React 18 之前,React 只会在事件回调中使用批处理,而在 Promise、setTimeout、原生事件等场景下,是不能使用批处理的。
而在 React 18 中,所有的状况预览,单厢自动使用批处理,不关心场景。
如果你在某种场景下不想使用批处理,你可以通过 flushSync来强制同步继续执行(比如说:你需要在状况预览后,立刻读取新 DOM 上的数据等。)
React 18 的批处理在绝大部分场景下是没有影响,但在 Class 组件中,如果你在两次 setState 中间读取了 state 值,会出现不相容的情况,如下实例。
当然你可以通过 flushSync来修正它。
流式 SSR
SSR 一次页面图形的业务流程大概为:
服务器 fetch 页面所需数据数据预备好之后,将组件图形成 string 形式作为 response 返回客户端加载资源客户端合成(hydrate)最终的页面内容在传统的 SSR 商业模式中,上述业务流程是以太网继续执行的,如果其中有一步比较慢,单厢影响整体的图形速度。
而在 React 18 中,基于全新的 Suspense,支持了流式 SSR,也就是允许服务端一点一点的返回页面。
假设他们有两个页面,包含了 NavBar、Sidebar、Post、Comments 等几个部分,在传统的 SSR 商业模式下,他们必须请求到 Post 数据,请求到 Comments 数据后,才能返回完整的 HTML。
但如果 Comments 数据请求很慢,会拖慢整个业务流程。
在 React 18 中,他们通过 Suspense包裹,可以告诉 React,他们不需要等这个组件,可以先返回其它内容,等这个组件预备好之后,单独返回。
如上,他们通过 Suspense包裹了 Comments 组件,那服务器首次返回的 HTML 是下面这样的,<Comments />组件处通过 loading进行了占位。
当 <Comments /> 组件预备好之后,React 会通过同两个流(stream)发送给浏览器(res.send 替换成 res.socket),并替换到相应位置。
更多关于流式 SSR 的讲解可见:https://github.com/reactwg/react-18/discussions/37
Server Component
Server Component 叫服务端组件,目前还在开发过程中,没有正式正式发布,不过如果很快就会和他们碰面的。
Server Component 的本质就是由服务端生成 React 组件,返回两个 DSL 给客户端,客户端解析 DSL 并图形该组件。
Server Component 带来的优势有:
1.零客户端体积,运行在服务端的组件只会返回最终的 DSL 信息,而不包含其他任何依赖。
假设他们有两个 markdown 图形组件,从前他们需要将依赖 marked和 sanitize-html打包到 JS 中。如果该组件在服务端运行,则最终返回给客户端的是转换完成的文本。
2.组件拥有完整的服务端能力
由于 Server Component 在服务端继续执行,拥有了完整的 NodeJS 的能力,可以访问任何服务端 API。3.组件支持实时预览
由于 Server Component 在服务端继续执行,理论上支持实时预览,类似于动态 npm 包,这个还是有比较大的想象空间的。或许 React Component as a service 时代来了。当然说了这么多好处,Server Component 肯定也是有一些局限性的:
不能有状况,也就是不能使用 state、effect 等,那么更适合用在纯展示的组件,对性能要求较高的一些前台业务不能访问浏览器的 APIprops 必须能被序列化OffScreen
OffScreen 目前也在开发中,会在未来某个版中正式发布。但他们非常有必要提前认识下它,因为你现在的代码很可能早已有问题了。
OffScreen 支持只保存组件的状况,而删除组件的 UI 部分。可以很方便的实现预图形,或者 Keep Alive。比如说他们在从 tabA 转换到 tabB,再返回 tabA 时,React 会使用之前保存的状况恢复组件。
为了支持这个能力,React 要求他们的组件对多次安装和销毁具有弹性。那什么样的代码不符合弹性要求呢?其实不符合要求的代码很常见。
在上面的代码中,如果发送请求时,组件卸载了,会抛出警告。
Warning: Cant perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
警告:不能在早已卸载的组件中更改 state。这是两个无用的操作,它表明你的项目中存在内存泄漏。要解决这个问题,请在 useEffect 清理函数中取消所有订阅和异步各项任务。
所以他们一般单厢通过两个 unmountRef来标记现阶段组件是否卸载,以避免所谓的「内存泄漏」。
他们来模拟继续执行一次组件,看看组件的变化状况:
首次加载时,组件的状况为:pending = false点击按钮后,组件的状况会变为:pending = true假如他们在请求过程中卸载了组件,那此时的状况会变为:pending = true在 OffScreen 中,React 会保存住最后的状况,下次会用这些状况重新图形组件。惨了,此时他们发现重新图形组件一直在 loading。
怎么解决?解决办法很简单,就是回归最初的代码,删掉 unmountRef的逻辑。至于「内存泄漏」的警告,React 18 删除了,因为这里不存在内存泄漏(参考:https://mp.weixin.qq.com/s/fgT7Kxs_0feRx4TkBe6G5Q)。
为了方便排查这类问题,在 React 18 的 Strict Mode 中,新增了 double effect,在开发商业模式下,每次组件初始化时,会自动继续执行一次卸载,重载。
这里还是要再提示下:开发环境,在 React 18 的严格商业模式下,组件初始化的 useEffect 会继续执行两次,也就是可能 useEffect 里面的请求被继续执行了两次等。
新 Hooks
useDeferredValue
useDeferredValue 可以让两个 state 延迟生效,只有现阶段没有紧急预览时,该值才会变为最新值。useDeferredValue 和 startTransition 一样,都是标记了一次非紧急预览。
之前 startTransition 的范例,就可以用 useDeferredValue来实现。
useId
支持同两个组件在客户端和服务端生成相同的唯一的 ID,避免 hydration 的不相容。原理是每个 id 代表该组件在组件树中的层级结构。
useSyncExternalStore
useSyncExternalStore 能够让 React 组件在 Concurrent Mode 下安全地有效地读取外接数据源。 在 Concurrent Mode 下,React 一次图形会分片继续执行(以 fiber 为单位),中间可能穿插优先级更高的预览。假如在高错误率的预览中改变了公共数据(比如说 redux 中的数据),那之前低优先的图形必须要重新早已开始继续执行,否则就会出现前后状况不一致的情况。 useSyncExternalStore
useInsertionEffect
这个 Hooks 只建议 css-in-js库来使用。 这个 Hooks 继续执行时机在 DOM 生成之后,useLayoutEffect 生效之前,一般用于提前注入 <style> 脚本。
如何升级到 React 18
参考:https://mp.weixin.qq.com/s/2QYEmFlIIMQkXR-Q9DVG2Q