React项目集成Web组件切换路由时报错:__reactFiber$xxx不可删除
现有两个部署在独立服务器的Web组件,单独访问均能正常显示:
服务器A
VIEW: abc.com/a/index.html CONFIGURATION abc.com/a/a.js abc.com/a/main.css <a-webcomponent>
服务器B
VIEW: abc.com/b/index.html CONFIGURATION abc.com/b/b.js abc.com/b/main.css <b-webcomponent>
将两者集成到新React项目中,在index.html中配置了两者的资源:
<!DOCTYPE html> <html lang="en"> <head> <script src="abc.com/a/a.js"></script> <link href="abc.com/a/main.css" rel="stylesheet"/> <script src="abc.com/b/b.js"></script> <link href="abc.com/b/main.css" rel="stylesheet"/> </head> <body> <div id="root"></div> </body> </html>
路由配置如下:
<Route path="/a-webcomponent" Component={AWebComponent} /> <Route path="/b-webcomponent" Component={BWebComponent} />
对应组件代码:
- AWebComponent:
return <a-webcomponent />
- BWebComponent:
return <b-webcomponent />
切换路由时出现报错:
Uncaught TypeError: property "__reactFiber$wqjvbwadizh" is non-configurable and can't be deleted
(中文翻译:未捕获的类型错误:属性"__reactFiber$wqjvbwadizh"不可配置,无法删除)
这个错误是React处理Web Component时的常见冲突:React会给DOM元素添加内部标识属性(如__reactFiber$xxx),而部分Web Component的销毁/重建逻辑可能会尝试修改或删除这些不可配置的属性,触发报错。以下是几种可行的解决办法:
1. 动态按需加载Web Component资源
不在index.html中一次性加载两个组件的资源,而是在对应路由组件挂载时再动态加载,切换路由时清理资源,避免全局环境冲突:
修改AWebComponent示例:
import { useEffect, useRef } from 'react'; function AWebComponent() { const containerRef = useRef(null); useEffect(() => { // 检查a.js是否已加载,未加载则动态创建标签引入 if (!document.querySelector('script[src="abc.com/a/a.js"]')) { const script = document.createElement('script'); script.src = 'abc.com/a/a.js'; document.head.appendChild(script); } // 同理加载CSS if (!document.querySelector('link[href="abc.com/a/main.css"]')) { const link = document.createElement('link'); link.href = 'abc.com/a/main.css'; link.rel = 'stylesheet'; document.head.appendChild(link); } // 组件卸载时清理资源 return () => { const script = document.querySelector('script[src="abc.com/a/a.js"]'); if (script) document.head.removeChild(script); const link = document.querySelector('link[href="abc.com/a/main.css"]'); if (link) document.head.removeChild(link); // 清理自定义元素定义(浏览器支持的话) if ('customElements' in window && customElements.get('a-webcomponent')) { customElements.define('a-webcomponent', class extends HTMLElement {}); customElements.delete('a-webcomponent'); } }; }, []); return <div ref={containerRef}><a-webcomponent /></div>; } export default AWebComponent;
BWebComponent做同样的修改,确保两个组件的资源仅在对应路由激活时存在于页面中。
2. 使用React Portal隔离Web Component
将Web Component挂载到独立的DOM节点上,避免React内部属性被Web Component的逻辑干扰:
import { useEffect, useRef, createPortal } from 'react'; function AWebComponent() { const portalRoot = useRef(null); useEffect(() => { // 创建独立的DOM节点作为Portal容器 const div = document.createElement('div'); document.body.appendChild(div); portalRoot.current = div; // 动态加载资源(同方法1的加载逻辑) if (!document.querySelector('script[src="abc.com/a/a.js"]')) { const script = document.createElement('script'); script.src = 'abc.com/a/a.js'; document.head.appendChild(script); } if (!document.querySelector('link[href="abc.com/a/main.css"]')) { const link = document.createElement('link'); link.href = 'abc.com/a/main.css'; link.rel = 'stylesheet'; document.head.appendChild(link); } // 卸载时清理容器和资源 return () => { if (portalRoot.current) { document.body.removeChild(portalRoot.current); } const script = document.querySelector('script[src="abc.com/a/a.js"]'); if (script) document.head.removeChild(script); const link = document.querySelector('link[href="abc.com/a/main.css"]'); if (link) document.head.removeChild(link); }; }, []); return portalRoot.current ? createPortal(<a-webcomponent />, portalRoot.current) : null; }
3. 调整Web Component的销毁逻辑(若有权限修改源码)
如果能拿到两个Web Component的源码,检查是否存在遍历DOM元素属性并全部删除的逻辑(比如Object.keys(element).forEach(key => delete element[key])),改成仅删除组件自身添加的属性,不要触碰React的内部标识属性。
内容的提问来源于stack exchange,提问作者Honey Singh

