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

运行时加载的React组件State无法同步更新问题排查

React本地组件与动态加载远程组件的State双向同步问题

问题背景

通过页面加载时添加脚本标签,将React组件的JS文件动态加载到目标仓库,期望实现本地组件的React State与远程组件双向同步:

  • 远程组件输入框输入内容时,本地State更新
  • 本地点击重置按钮时,远程组件输入框同步更新State

远程组件代码

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

interface RemoteCompProps {
  text: string;
  setText: (value: string) => void;
}

function RemoteComp({ text, setText }: RemoteCompProps) {
  console.log("text (in remote comp):\n", text);
  return (
    <input
      type="text"
      value={text}
      onChange={(event) => setText(event.target.value)}
    />
  );
}

export interface AssetExportConfig {
  remoteCompProps: RemoteCompProps;
  domId: string;
}

export const assetExport = {
  renderToDOM: (config: AssetExportConfig) => {
    createRoot(document.getElementById(config.domId) as HTMLElement).render(
      <RemoteComp {...config.remoteCompProps} />
    );
  },
  assetName: 'Remote-Comp'
};

本地组件代码

const ASSET_URL = 'http://localhost:5173/ext-assets';
const ASSET_NAME = 'Remote-Comp';
const EXT_COMP_ID = 'aBC123';

const loadAsset = () => {
    const scriptId = `script-${ASSET_NAME}`;
    let script = document.getElementById(`script-${ASSET_NAME}`) as HTMLScriptElement;
    script = document.createElement('script');
    script.src = `${ASSET_URL}/${ASSET_NAME}.js`;
    script.id = scriptId;
    script.type = 'text/javascript';
    document.head.appendChild(script);
};

export function LocalPage() {
    loadAsset();
    const [text, setText] = useState('First');

    const extCompElement = window[ASSET_NAME];

    useEffect(() => {
        if (extCompElement) {
            extCompElement?.assetExport?.renderToDOM({
                remoteCompProps: {
                    text,
                    setText,
                },
                domId: EXT_COMP_ID,
            });
        }
    }, [extCompElement]);

    console.log('text (in local comp):\n', text);

    return (
        <div>
            <div id={EXT_COMP_ID} />
            <button onClick={() => setText('First')}>Reset text</button>
        </div>
    );
}

现象与疑问

  • 实际现象:远程组件加载成功,远程输入框输入内容时本地text State正常更新;但点击本地重置按钮,本地text State更新后,远程组件输入框值未同步更新。
  • 疑问:是否是手动渲染远程组件导致的问题?如何让远程组件同步更新State?

问题原因

核心问题在于远程组件的渲染触发时机不足:
当前useEffect仅依赖extCompElement,也就是只有当远程脚本加载完成、extCompElement存在时才会执行一次renderToDOM。当本地text State变化时,useEffect不会重新触发,远程组件无法接收到新的text props,因此不会更新。

另外,loadAsset函数每次LocalPage组件渲染都会执行,会重复添加相同的脚本标签,可能引发不必要的重复加载。

解决方案

1. 调整useEffect依赖,触发远程组件更新

将text加入useEffect的依赖数组,这样每次text State变化时,都会重新调用renderToDOM,将最新的text传递给远程组件。

2. 优化脚本加载逻辑,避免重复添加

在loadAsset中先判断脚本是否已存在,存在则不再重复创建。

修改后的本地组件代码

const ASSET_URL = 'http://localhost:5173/ext-assets';
const ASSET_NAME = 'Remote-Comp';
const EXT_COMP_ID = 'aBC123';

const loadAsset = () => {
    const scriptId = `script-${ASSET_NAME}`;
    // 先检查脚本是否已存在,避免重复加载
    if (document.getElementById(scriptId)) return;
    
    const script = document.createElement('script');
    script.src = `${ASSET_URL}/${ASSET_NAME}.js`;
    script.id = scriptId;
    script.type = 'text/javascript';
    document.head.appendChild(script);
};

export function LocalPage() {
    // 用useEffect执行脚本加载,避免每次渲染都调用
    useEffect(() => {
        loadAsset();
    }, []);
    
    const [text, setText] = useState('First');
    const extCompElement = window[ASSET_NAME];

    useEffect(() => {
        if (extCompElement) {
            extCompElement?.assetExport?.renderToDOM({
                remoteCompProps: {
                    text,
                    setText,
                },
                domId: EXT_COMP_ID,
            });
        }
    }, [extCompElement, text]); // 添加text到依赖数组

    console.log('text (in local comp):\n', text);

    return (
        <div>
            <div id={EXT_COMP_ID} />
            <button onClick={() => setText('First')}>Reset text</button>
        </div>
    );
}

额外优化(可选)

远程组件的renderToDOM中,createRoot其实可以只执行一次,后续复用root实例进行更新,这样更符合React的渲染逻辑:

// 远程组件代码修改:缓存root实例
let rootInstance: ReturnType<typeof createRoot> | null = null;

export const assetExport = {
  renderToDOM: (config: AssetExportConfig) => {
    const domElement = document.getElementById(config.domId) as HTMLElement;
    if (!rootInstance) {
      rootInstance = createRoot(domElement);
    }
    rootInstance.render(<RemoteComp {...config.remoteCompProps} />);
  },
  assetName: 'Remote-Comp'
};

这样每次调用renderToDOM时,会复用已创建的root实例,避免重复初始化,性能更优。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 08:17:39