如何在<custom-web-component />中获取React路由上下文?
解决方案:通过DOM节点定位React Fiber并提取路由上下文
核心思路是利用React内部绑定在DOM节点上的Fiber引用,向上遍历React Fiber树,找到Web组件父级对应的路由上下文。以下是具体实现步骤:
1. 工具函数:从DOM节点获取路由上下文
编写工具函数,接收Web组件的DOM节点,遍历其对应的React Fiber树,提取UNSAFE_RouteContext:
import { UNSAFE_RouteContext } from 'react-router-dom'; function getParentRouteContext(domNode) { // 找到DOM节点对应的React Fiber(React内部隐藏属性,后缀为随机字符串) let fiber = Object.values(domNode).find(val => val?.type?.toString().includes('FiberNode') ); if (!fiber) return null; // 向上遍历Fiber树,查找路由上下文 while (fiber) { // 检查当前Fiber的上下文或memoizedProps中是否包含路由上下文 if (fiber.context && fiber.context[UNSAFE_RouteContext._currentValue]) { return fiber.context[UNSAFE_RouteContext._currentValue]; } // 也可能在Provider的memoizedProps里 if (fiber.type === UNSAFE_RouteContext.Provider) { return fiber.memoizedProps.value; } fiber = fiber.return; } return null; }
2. 在Web组件的connectedCallback中获取上下文并传递给Manager
修改自定义Web组件,在connectedCallback中调用工具函数获取路由上下文,然后连同DOM节点一起通知Manager:
class CustomWebComponent extends HTMLElement { connectedCallback() { const routeContext = getParentRouteContext(this); // 通知Manager组件:需要注入Foo,并携带路由上下文 window.dispatchEvent(new CustomEvent('portal-request', { detail: { target: this, routeContext } })); } } customElements.define('custom-web-component', CustomWebComponent);
3. 在Manager中使用路由上下文包裹Portal内容
Manager组件监听自定义事件,拿到路由上下文后,用UNSAFE_RouteContext.Provider包裹Foo再通过createPortal注入:
import { createPortal, useLayoutEffect, useState } from 'react'; import { UNSAFE_RouteContext } from 'react-router-dom'; import Foo from './Foo'; function Manager() { const [portalTargets, setPortalTargets] = useState([]); useLayoutEffect(() => { const handlePortalRequest = (e) => { const { target, routeContext } = e.detail; setPortalTargets(prev => [...prev, { target, routeContext }]); // 清理:当Web组件断开连接时移除 const handleDisconnect = () => { setPortalTargets(prev => prev.filter(item => item.target !== target)); target.removeEventListener('disconnected', handleDisconnect); }; target.addEventListener('disconnected', handleDisconnect); }; window.addEventListener('portal-request', handlePortalRequest); return () => window.removeEventListener('portal-request', handlePortalRequest); }, []); return portalTargets.map(({ target, routeContext }, index) => { if (!routeContext) return createPortal(<Foo />, target); // 用父级路由上下文包裹Foo return createPortal( <UNSAFE_RouteContext.Provider value={routeContext}> <Foo /> </UNSAFE_RouteContext.Provider>, target, index // 唯一key ); }); }
注意事项
- 此方案依赖React内部未公开的
__reactFiber$xxx属性,不同React版本(尤其是18+)可能存在结构变化,升级React时需要测试验证。 - 如果Web组件完全不在React树中(比如直接通过原生JS插入DOM),则无法找到对应的Fiber节点,此时只能退回到根路由上下文。
内容的提问来源于stack exchange,提问作者Jason Kleban
相关产品推荐
相关产品推荐

