运行时加载的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
相关产品推荐
相关产品推荐

