You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确卸载使用createRoot创建的React根实例?

React 18 createRoot 卸载时触发同步卸载警告的解决方案

问题重现

我有一个组件,内部频繁调用React 18的createRoot渲染内容。在componentWillUnmount中调用unmount卸载时,会触发以下警告:

Warning: Attempted to synchronously unmount a root while React was already rendering. React cannot finish unmounting the root until the current render has completed, which may lead to a race condition.

该代码在React 17使用ReactDOM.render()时无此警告,最小复现代码如下:

import React from "react";
import { createRoot } from "react-dom/client";

class Example extends React.Component {
  componentWillUnmount() {
    if (this._root) {
      // 触发警告的代码
      this._root.unmount();
    }
  }
  render() {
    return (
      <div
        ref={(el) => {
          if (el) {
            console.log("creating root");
            this._root = createRoot(el);
            this._root.render(<div>rendered with createRoot</div>);
          }
        }}
      />
    );
  }
}
class Example2 extends React.Component {
  render() {
    // 触发重渲染的示例代码
    return (
      <div>
        {this.state?.aa ? <div>bbb</div> : <Example />}
        <button onClick={() => this.setState({ aa: true })}>click me</button>
      </div>
    );
  }
}
createRoot(document.getElementById("app")).render(<Example2 />);

原因分析

React 18引入了并发渲染机制,createRoot创建的根节点卸载操作unmount是异步执行的逻辑。而componentWillUnmount属于React同步渲染流程中的生命周期钩子,此时直接调用unmount会导致渲染流程与卸载操作冲突,触发竞态条件警告。React 17的ReactDOM.render没有并发渲染机制,因此不会出现该问题。

解决方案

方法1:用setTimeout延迟卸载

将unmount放入宏任务队列,避开当前同步渲染流程:

componentWillUnmount() {
  if (this._root) {
    setTimeout(() => {
      this._root.unmount();
      this._root = null;
    }, 0);
  }
}

方法2:改用函数组件+useEffect清理(推荐)

将类组件转为函数组件后,利用useEffect的清理函数处理卸载——该清理逻辑会在渲染完成后异步执行,不会与当前渲染流程冲突:

import React, { useEffect, useRef } from "react";
import { createRoot } from "react-dom/client";

const Example = () => {
  const containerRef = useRef(null);
  const rootRef = useRef(null);

  useEffect(() => {
    if (containerRef.current) {
      rootRef.current = createRoot(containerRef.current);
      rootRef.current.render(<div>rendered with createRoot</div>);
    }

    return () => {
      if (rootRef.current) {
        rootRef.current.unmount();
      }
    };
  }, []);

  return <div ref={containerRef} />;
};

方法3:使用ReactDOM.unmountComponentAtNode兼容写法

如果需要同时兼容React 17和18,可使用该方法替代root.unmount(),它在React 18中能正常工作且不会触发警告:

import ReactDOM from 'react-dom';

componentWillUnmount() {
  if (this._root && this._root._internalRoot?.containerInfo) {
    ReactDOM.unmountComponentAtNode(this._root._internalRoot.containerInfo);
    this._root = null;
  }
}

注意:_internalRoot是React内部属性,后续版本可能变动,需谨慎使用。

内容的提问来源于stack exchange,提问作者sydd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 07:24:12