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的目标容器
问题原因
- 初始渲染时容器未挂载:首次渲染时
PortalExit的DOM还未完成挂载,ref.current为null,此时PortalEntrance直接返回空片段。但ref本身是稳定的引用类型,后续ref.current更新时不会触发PortalEntrance重新渲染,导致内容永远不会被创建为Portal。 - 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);
关键修复点
- 状态替代ref传递:将原本的
ref对象替换为useState状态,当PortalExit的div挂载时,通过ref回调函数直接更新状态,触发Context的value更新。 - 自动触发组件重渲染:当
portalContainer状态变化时,PortalEntrance作为Context消费者会自动重新渲染,此时容器已存在,就能正确创建Portal并渲染内容。 - 简化逻辑:移除了不必要的
useMemo缓存,因为状态更新本身会驱动Context的重新传递,无需额外优化。
内容的提问来源于stack exchange,提问作者Page not found
相关产品推荐
相关产品推荐

