支持混用

react 17 最主要的改动仍然是要支持渐进式,你可以在一个应用中使用 react17,另外一个使用 react 18。

  1. const rootNode = document.getElementById('root');
  2. ReactDOM.render(<AppReact17 />, rootNode);
  3. const rootNode2 = document.getElementById('root2');
  4. ReactDOM.render(<AppReact18 />, rootNode);

react 官方要支持这种用法,推测的 react18 的改动将会巨大无比,甚至于完全不兼容旧的。

更改事件委托

原来的 react 的事件系统是挂在 document 上的,为了支持混用将会修改到你挂载的 rootNode 中,这样其实会造成一些问题,比如说原来的e.stopPropagation() 能解除原生 document 事件的冒泡,现在只能阻止到 rootNode,但大部分时候是没有影响的。

image.png

portals 的事件还会继续监听,当然还有一些小更新

  • onScroll事件不再冒泡,以防止出现一些混淆
  • React的onFocus和onBlur事件已在底层切换为原生的focusin和focusout事件。它们更接近React现有行为,有时还会提供额外的信息。
  • 捕获事件(例如,onClickCapture)现在使用的是实际浏览器中的捕获监听器
  • 去除事件池,在现在浏览器上,这个功能不会提升性能,反而会因为重用事件对象,造成困扰。
  • focus 事件底层切换成了 focusin,行为没有变化。

总体而言,这次改动会让 react 的事件树更符合 dom 的规范,虽然会导致很多面试题不能用了。

useEffect 的回调修改为异步调用

useEffect 的副作用清理函数是在 effect 执行之后立马执行的,但是在使用中发现了如果回调中的操作比较耗时,会造成一些性能问题,现在useEffect 的 副作用清理函数会在 render 后执行了。

官方提供了一个 demo 来展示可能会造成的问题,我们一般也不这么用。如果有特殊情况,可以用 useLayoutEffect 来代替。

  1. useEffect(() => {
  2. const instance = someRef.current;
  3. instance.someSetupMethod();
  4. return () => {
  5. instance.someCleanupMethod();
  6. };
  7. });

返回一致的undefined错误

这个改动几乎无感,以前,React只对class和函数组件执行此操作,但并不会检查forwardRef和memo组件的返回值。现在都会检查,不要写奇怪的代码是不会挂的。最好用 return null 来代替。

展望一下 18

自从 2016 年 Seb 提出了 fiber 架构以来,react 已经吊足了人们的胃口 4 年了。react16 重写了 react 的渲染引擎,一直到最近发布的 17,看起来 async component 终于快要发布了。这几年虽然有了 hooks,但是仍然没有 fiber 来的让人激动。

fiber 架构主要还是用来对 cpu 和网络的问题,这两个问题一直也是最影响前端开发体验的地方,一个会造成卡顿,一个会造成白屏。为此 react 为前端引入了两个新概念。

Time Slicing 时间分片

时间分片是利用了 fiber 的可中断,可继续的功能,每个渲染周期内都会留一部分的时间来响应用户的输入,或者其他 IO 的状态修改。

  • React 在渲染(render)的时候,不会阻塞现在的线程
  • 如果你的设备足够快,你会感觉渲染是同步的
  • 如果你设备非常慢,你会感觉还算是灵敏的
  • 虽然是异步渲染,但是你将会看到完整的渲染,而不是一个组件一行行的渲染出来
  • 同样书写组件的方式

Suspense

  1. // 这不是一个 Promise。这是一个支持 Suspense 的特殊对象。
  2. const resource = fetchProfileData();
  3. function ProfileDetails() {
  4. // 尝试读取用户信息,尽管该数据可能尚未加载
  5. const user = resource.user.read();
  6. return <h1>{user.name}</h1>;
  7. }
  8. function ProfileTimeline() {
  9. // 尝试读取博文数据,尽管数据可能未加载完毕
  10. const posts = resource.posts.read(); return (
  11. <ul>
  12. {posts.map(post => (
  13. <li key={post.id}>{post.text}</li>
  14. ))}
  15. </ul>
  16. );
  17. }
  18. function ProfilePage() {
  19. return (
  20. <Suspense fallback={<h1>Loading profile...</h1>}>
  21. <ProfileDetails />
  22. <Suspense fallback={<h1>Loading posts...</h1>}>
  23. <ProfileTimeline />
  24. </Suspense>
  25. </Suspense>
  26. );
  27. }

**
react 的思路已经拓宽了整个前端的边界,18 发布之后虽然会造成很多问题,比如 antd 的动画就一定会挂。但是默认的性能提升可以让很多中后台获得性能加成, 很多项目其实最后也不会有机会进行细致的优化。Suspense 更是会更改我们前端写代码的范式,期待早点发布。

参考文档

https://reactjs.org/blog/2020/08/10/react-v17-rc.html
https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html
https://github.com/acdlite/react-fiber-architecture
https://zh-hans.reactjs.org/docs/concurrent-mode-suspense.html#approach-1-fetch-on-render-not-using-suspense