You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

React 19升级后,useEffect内通过ReactDOM.createRoot渲染的组件首次加载不显示且后续行为异常

React 19升级后,useEffect内通过ReactDOM.createRoot渲染的组件首次加载不显示且后续行为异常

兄弟,这种情况我太熟了!升级React19后碰到SurveyJS自定义组件挂载异常,大概率是React19对根节点渲染的时机和生命周期做了更严格的限制,和你之前React18的写法不兼容了,我给你拆解下问题和解决办法:

问题根源分析

你之前在React18里,通过useEffect配合ReactDOM.createRoot手动把组件挂载到SurveyJS插入的.editViewContainer里,这种写法在React18里没问题,因为React18对根节点的创建时机和重复创建的容忍度比较高。但React19为了优化渲染性能,对根节点的管理更严格:

  • 首次打开Survey时,你的useEffect可能比SurveyJS插入DOM节点的时机更早,这时候querySelector('.editViewContainer')拿不到元素,自然渲染不了;
  • 后续重新打开时,DOM节点存在了,但你可能重复对同一个节点调用createRoot,React19里这种重复创建会导致渲染上下文混乱,所以组件行为变得不可预测。

具体解决办法

1. 用MutationObserver精准监听DOM节点的出现

与其依赖useEffect的执行时机,不如直接监听SurveyJS插入容器节点的动作,确保节点存在后再创建根节点,还能避免重复创建:

useEffect(() => {
  // 监听body下的DOM变化,捕获SurveyJS插入的容器
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      // 检查是否有新节点被插入
      if (mutation.addedNodes.length > 0) {
        const targetContainers = document.querySelectorAll('.editViewContainer');
        targetContainers.forEach(container => {
          // 给容器标记是否已创建过React根节点,避免重复操作
          if (!container.hasAttribute('data-react-root-initialized')) {
            const root = ReactDOM.createRoot(container);
            root.render(<EditView />);
            container.setAttribute('data-react-root-initialized', 'true');
          }
        });
      }
    });
  });

  // 监听整个body的子节点变化,包括子树
  observer.observe(document.body, {
    childList: true,
    subtree: true,
    attributes: false,
    characterData: false
  });

  // 组件卸载时断开监听,避免内存泄漏
  return () => observer.disconnect();
}, []);

2. 改用SurveyJS官方规范的自定义组件注册方式

其实你完全不用手动挂载DOM,SurveyJS的ComponentCollection.Instance.add支持直接传入React组件,让SurveyJS自己负责渲染,这种方式天然适配React19的渲染机制:

// 假设你原来的自定义组件名叫"geomet"
ComponentCollection.Instance.add({
  name: "geomet",
  // 直接传入你的EditView组件,SurveyJS会在对应的容器里自动渲染
  component: EditView,
  // 这里可以加其他自定义配置,比如组件的属性映射等
  // ...
});

这种写法不需要你手动操作ReactDOM的根节点,完全交给SurveyJS和React19的渲染流程处理,从根源上避免挂载时机和重复创建的问题。

3. 手动管理根节点的更新(如果必须手动挂载)

如果因为业务原因必须手动挂载,那要确保每次更新时用同一个根节点,而不是重复创建:

useEffect(() => {
  const containers = document.querySelectorAll('.editViewContainer');
  containers.forEach(container => {
    let root = container._reactRoot;
    if (!root) {
      root = ReactDOM.createRoot(container);
      container._reactRoot = root;
    }
    // 用root.render更新,而不是每次创建新的根节点
    root.render(<EditView />);
  });

  return () => {
    // 组件卸载时清理根节点
    const containers = document.querySelectorAll('.editViewContainer');
    containers.forEach(container => {
      if (container._reactRoot) {
        container._reactRoot.unmount();
        delete container._reactRoot;
      }
    });
  };
}, [/* 这里加入EditView依赖的状态,确保状态变化时重新渲染 */]);

内容来源于stack exchange

火山引擎 最新活动