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

如何在<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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 14:32:39