React18 为什么要用 createRoot 取代 render

3,599 阅读2分钟

在 React 18 中,ReactDOM.render 方法已经被标记为遗弃的方法了,下一个大版本应该就被移除了,现在在使用的时候会出现警告:

image.png

取而代之的,是使用 ReactDOM.createRoot

const root = ReactDOM.createRoot(rootElement);
root.render(<App />);

为什么要做这件事呢?

我们之前的 render 使用是这样的:

ReactDOM.render(<App />, rootElement,);

当我们运行上面这句代码的时候,React 会为我们创建一个顶级的根节点,叫做 FiberRoot,这个根节点是绑定到 rootElement 这个 DOM 节点上:


// container 对应的就是 `ReactDOM.render` 的第一个参数
container._reactRootContainer = legacyCreateRootFromDOMContainer(
  container,
  forceHydrate,
);

这样的话,就算我们没有改变 DOM 根节点 rootElement,当我们再想重新渲染整个应用的时候,还是要把传递 rootElement。并且,我们的 React 应用的根节点对使用者来说是不透明的,我们无法取到这个值。

而当我们使用 createRoot 了之后呢,我们需要在第一次的时候传入 rootElement 节点:

const root = ReactDOM.createRoot(rootElement);

后续我们渲染整个应用的时候只需要调用 rootrender 方法就好了,React 应用的根节点也向我们暴露了出来,我们就不必再把它隐式的绑定到它到对应的 DOM 节点上去了。

root.render(<App />);

React 18 最大的优化可能莫过于 Concurrent Mode 的到来了,经过 17 版本的过渡,它终于在 18 版本到来了。为了考虑老项目的兼容性,强硬的让大家升级肯定是不行的,最好的是新版本依然不痛不痒的升级。为了更大程度的推行它,也可能是单独开一个 createRoot API 的原因。

还有一个改动可能需要注意一下,之前的 render 方法接受一个 callback

ReactDOM.render(container, <App />, function() {
  // 初始渲染或者后续更新会调用这里
  console.log('rendered').
});

但是 createRoot 移除了它,如果你还是有类似的需求,可以考虑下面几种方案其一:

1. 使用 ref

使用这种方案,会在我们的组件第一次挂载到 DOM 上去的时候调用。

function App({ callback }) {
  // div 第一次挂载到 DOM 那个操作的时候会调用 callback
  return (
    <div ref={callback}>
      <h1>Hello World</h1>
    </div>
  );
}

const rootElement = document.getElementById("root");
const root = ReactDOM.createRoot(rootElement);
root.render(<App callback={() => console.log("renderered")} />);

2. 使用 requestIdleCallback

这个方法就相当于给浏览器增加了一个低优先级的任务,会在浏览器当前帧还有空闲的时候执行,具体什么时候执行也是比较未知。

ReactDOM.createRoot(rootElement).render(<App />);
requestIdleCallback(callback);

3. 使用 setTimeout

会在 React 第一次被中断任务的时候被执行。

ReactDOM.createRoot(rootElement).render(<App />);
setTimeout(callback, 0);

我觉得第一种使用 ref 的方案比较好用一点。

最后,和你一同期待 createRoot 在项目中的应用。