React18的新特性

358 阅读5分钟

一、发版回顾

2017.9 React16加入Fiber架构;

2020.10 React17发布,作为垫脚石版本,谨慎地探索新特性,支持渐进式升级;

2022.3 React18发布,正式开启并发更新。

虽然两三年一版本,看起来更新很慢,但影响特别大,中间的提案过程很多,考虑得比较谨慎。

二、变更型新特性

  1. createRoot

  • ReactDOM.render换成ReactDOM.createRoot,不换的话会有警告
  • 去掉了callback,避免使用Suspense时出现问题
  • 这个新入口允许使用并发功能
  • 对应该的unmountComponentAtNode需换成root.unmount
// React 17 

const root = document.getElementById('root');

ReactDOM.render(<App />, root, () => { console.log('执行 callback') }) 

// React 18 

const AppWithCallback = () => { 

  useEffect(() => { console.log('执行 callback'); }, [])

  return <App />

}

const container = document.getElementById('root')

const root = ReactDOM.createRoot(container)

root.render(<AppWithCallback />)
  1. SSR hydrateRoot

  • hydrate替换为hydrateRoot
  • 支持服务端Suspense/流式SSR,优化了相关API
// 之前

const container = document.getElementById('app')

ReactDOM.hydrate(<App />, container)

// 之后

const container = document.getElementById('app');

const root = ReactDOM.hydrateRoot(container, <App />);

// 和createRoot不一样,此处不再需要root.render()
  1. Strict Mode

  • 严格模式下每个组件会两次渲染,以便观察潜在问题;安装React DevTools后,第二次渲染的日志信息将显示为灰色
  • 这个模式帮助在开发过程中发现与并发相关的错误,检查可重用状态和不合适的写法,比如state不变,组件卸载重载
* 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
  1. 自动批处理

  • 将多个状态更新合并与一次重新渲染,以获得更好的性能
  • React 18之前,React 只能在组件的生命周期函数或者合成事件函数中进行批处理。默认情况下,Promise、setTimeout 以及原生事件中是不会进行批处理的。如果需要使用批处理,可以用 unstable_batchedUpdates,但它不是正式API
  • React 18之后,在并发模式下,任何情况都会自动执行批处理
  • 如果需要退出批量更新,可以使用ReactDOM.flushSync手动退出,即强制要求立即触发重新渲染,这样可以精准控制更新
  • 更详细示例可查看:github.com/reactwg/rea…
function App() {

  const [count, setCount] = useState(0);

  const [flag, setFlag] = useState(false);



  function handleClick() {

    setCount(c => c + 1); // 状态更新,不触发渲染

    setFlag(f => !f); // 状态更新,不触发渲染

    // 结束的时候,才触发一次重新渲染,即之前的更新批量的做一次重新渲染

  }



  return (

    <div>

      <button onClick={handleClick}>Next</button>

      <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>

    </div>

  );

}
function handleClick() {

  flushSync(() => {

    setCount(2);

  });

  // 会在 setCount(2) 并 render 之后再执行 setCount(3)

  setCount(3);

}

三、新的并发特性

  1. 并发模式

并发模式(Concurrent Mode)16就有了,17通过试验性API开启,做了大量渐进升级。

并发模式不是一个功能,而是一种底层设计。

并发模式可帮助应用保持响应,并根据用户的设备性能和网速进行适当的调整,以保证最高的快速响应。

16的Fiber架构之前,是不可中断的递归方式更新。之后,使用了可中断的遍历方式更新,使并发更新成为可能。

但是所谓并发模式,最终要落地的是,自动批处理、以及一些并发更新特性。并发特性包括:useTransition、useDeferredValue,可以分开启用。18通过并发特性的开关,来启用并发更新。

总结一下,开启并发模式,即可开启自动批处理;在并发模式下,使用相应的并发特性,才能开启并发更新。

这三个词是渐进升级过程的产物,最终都说的一个意思,让应用做到响应性更好。

const App = () => {

  const [count, updateCount] = useState(0);

  const [isPending, startTransition] = useTransition();



  const onClick = () => {

    // 使用了并发特性useTransition

    startTransition(() => {

      // 本次更新是并发更新

      // 如果updateCount没有作为startTransition的回调函数执行,那么updateCount将触发默认的同步更新。

      updateCount((count) => count + 1);

    });

  };

  return <h3 onClick={onClick}>{count}</h3>;

};

  1. useTransition

  • startTransition能在大量的任务下也能保持 UI 响应,比如在不同视图之间进行导航切换,不阻止用户输入
  • 实现原理是,startTransition 回调中的 setState 触发的渲染被标记为不紧急渲染,这些渲染能被其他紧急渲染抢占
  • 这一API很可能会由各种Router实现,再作为一个配置项开放给开发者
  • 详情参考:github.com/reactwg/rea…
  1. useDeferredValue

  • 返回一个延迟响应的值,通常用于用户输入要立即渲染场景,或要等待数据获取时,保持接口的可响应性。
  • useDeferredValue 内部实现与 useTransition 一样,都是标记了延迟更新任务
  • useTransition 是把更新任务变成了延迟更新任务,而 useDeferredValue 是产生一个新的值,这个值作为延时状态;一个用来包装方法,一个用来包装值
  • 相比于debounce的手动设定延时值,useDeferredValue 延时值是高优渲染耗时值,在性能好的机器上比较小,这样能快速响应,在性能不好的机器上比较大,这样能适当延迟、避免执行阻塞。即,它是一个自适应的最佳的值,比手动设置dobounce的响应性更优。

四、新的Hook API

定义了一些新的 API,以满足三方库在并发模式下特定场景的诉求。

比如styles管理、外部状态管理、可访问性(accessibility)等场景。

  1. useId

支持在客户端和服务端生成唯一ID,主要为解决流式SSR返回的 HTML 片段无序的问题。

  1. useSyncExternalStore

允许外部状态管理器,强制立即同步,以支持并发读取。

  1. useInsertionEffect

在DOM生成之后、LayoutEffect执行之前执行,用于解决CSS-in-JS库在渲染中动态注入样式的性能问题。