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

React TypeScript中createPortal+useContext实现的Wormhole无法渲染内容

React TypeScript中createPortal结合useContext实现传送门不渲染的问题修复

问题场景

在React TypeScript项目中,尝试通过createPortal结合useContext实现“传送门”功能,但内容始终无法渲染。需求是将PortalEntrance的所有子元素渲染到PortalExit所在的DOM位置。

原本的实现思路:

  • PortalProvider提供一个ref对象
  • PortalExit将该ref绑定到自身的子div元素上
  • PortalEntrance使用这个ref.current作为createPortal的目标容器

问题原因

  1. 初始渲染时容器未挂载:首次渲染时PortalExit的DOM还未完成挂载,ref.current为null,此时PortalEntrance直接返回空片段。但ref本身是稳定的引用类型,后续ref.current更新时不会触发PortalEntrance重新渲染,导致内容永远不会被创建为Portal。
  2. Context未感知ref变化:PortalProvider中用useMemo缓存Context值时,依赖的是ref对象本身(而非ref.current),而ref对象在组件生命周期内不会改变,因此Context的消费者无法感知到容器元素的更新。

修复方案

核心思路

改用状态变量跟踪Portal容器元素,利用React状态更新触发Context重新传递,确保PortalEntrance能感知到容器元素的挂载状态并重新渲染。

修复后的完整代码

import type { ReactNode } from "react";
import * as React from "react";
import { createPortal, render } from "react-dom";

type PortalContextType = {
  portalContainer: HTMLDivElement | null;
  setPortalContainer: (container: HTMLDivElement | null) => void;
};

const PortalContext = React.createContext<PortalContextType>({
  portalContainer: null,
  setPortalContainer: () => {},
});

type PortalProviderProps = {
  children: ReactNode;
};

function PortalProvider({ children }: PortalProviderProps) {
  const [portalContainer, setPortalContainer] = React.useState<HTMLDivElement | null>(null);

  return (
    <PortalContext.Provider value={{ portalContainer, setPortalContainer }}>
      {children}
    </PortalContext.Provider>
  );
}

function PortalExit() {
  const { setPortalContainer } = React.useContext(PortalContext);

  // 用ref回调直接更新状态,替代传统的ref对象
  return <div ref={setPortalContainer}></div>;
}

type PortalEntranceProps = {
  children: ReactNode;
};

function PortalEntrance({ children }: PortalEntranceProps) {
  const { portalContainer } = React.useContext(PortalContext);

  // 容器不存在时返回null,存在时创建Portal
  if (!portalContainer) {
    return null;
  }

  return createPortal(children, portalContainer);
}

function App() {
  return (
    <PortalProvider>
      <div style={{ border: "1px solid green" }}>
        <PortalExit />
      </div>
      <div style={{ border: "1px solid red" }}>
        <PortalEntrance>my content</PortalEntrance>
      </div>
    </PortalProvider>
  );
}

const rootElement = document.getElementById("root");
render(<App />, rootElement);

关键修复点

  1. 状态替代ref传递:将原本的ref对象替换为useState状态,当PortalExit的div挂载时,通过ref回调函数直接更新状态,触发Context的value更新。
  2. 自动触发组件重渲染:当portalContainer状态变化时,PortalEntrance作为Context消费者会自动重新渲染,此时容器已存在,就能正确创建Portal并渲染内容。
  3. 简化逻辑:移除了不必要的useMemo缓存,因为状态更新本身会驱动Context的重新传递,无需额外优化。

内容的提问来源于stack exchange,提问作者Page not found

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 10:08:19