如何在React的createPortal函数中向子组件传递Props——以WindowPortal传递窗口实例处理resize事件为例
props.children in a React Portal Great question! To pass the newWindow.current instance as an externalWindow prop to your child components in the portal, you'll need to clone the child elements and inject the additional props using React's cloneElement and Children.map utilities. Here's how to implement this step-by-step:
Step 1: Clone Child Elements with Injected Props
Instead of directly rendering props.children, we'll iterate over each child (in case there are multiple) and clone them with the externalWindow prop attached. This ensures every child component gets access to the new window instance.
Step 2: Fix Closure Issues in Resize Handler
Also, note that your ExpandedComponentForWindowPortal has a small closure issue with the resize handler. Using a functional update for setCountOfResize and cleaning up the event listener properly will prevent bugs.
Modified WindowPortal Component
Here's the updated code for your WindowPortal component, focusing on the return statement where we pass the prop:
import React, { useState, useRef, useEffect, useCallback, Children, cloneElement } from "react"; import { createPortal } from "react-dom"; import { create } from "jss"; import { jssPreset, StylesProvider, CssBaseline } from "@material-ui/core"; type WindowPortalProps = { width: number; height: number; close: () => void; id: number; title: string; children: React.ReactNode; }; const WindowPortal: React.FC<WindowPortalProps> = (props) => { const [container, setContainer] = useState<HTMLElement | null>(null); const newWindow = useRef<Window | null>(null); const { title } = props; const [jss, setJss] = useState<any>(null); useEffect(() => { setContainer(document.createElement("div")); }, []); const close = useCallback(() => { props.close(); }, [props]); useEffect(() => { if (container) { setJss(create({ ...jssPreset(), insertionPoint: container })); newWindow.current = window.open( "", "", `width=${props.width},height=${props.height},left=200,top=200,scrollbars,resizable,menubar,toolbar,location`, ) as Window; newWindow.current.document.body.appendChild(container); newWindow.current.document.title = title; const stylesheets = Array.from(document.styleSheets); stylesheets.forEach((stylesheet) => { const css = stylesheet as CSSStyleSheet; const owner = stylesheet.ownerNode as HTMLElement; if (owner.dataset.jss !== undefined) { return; } if (stylesheet.href) { const newStyleElement = document.createElement("link"); newStyleElement.rel = "stylesheet"; newStyleElement.href = stylesheet.href; newWindow.current?.document.head.appendChild(newStyleElement); } else if (css && css.cssRules && css.cssRules.length > 0) { const newStyleElement = document.createElement("style"); Array.from(css.cssRules).forEach((rule) => { newStyleElement.appendChild(document.createTextNode(rule.cssText)); }); newWindow.current?.document.head.appendChild(newStyleElement); } }); const curWindow = newWindow.current; curWindow.addEventListener("beforeunload", close); return () => { curWindow.close(); curWindow.removeEventListener("beforeunload", close); }; } }, [container, props.width, props.height, title, close]); // Clone children and pass externalWindow prop const renderedChildren = Children.map(props.children, (child) => { if (React.isValidElement(child)) { return cloneElement(child, { externalWindow: newWindow.current }); } return child; }); return ( container && newWindow.current && jss && createPortal( <StylesProvider jss={jss} sheetsManager={new Map()}> <CssBaseline /> {renderedChildren} </StylesProvider>, container, ) ); };
Updated ExpandedComponentForWindowPortal
Fix the resize handler to avoid closure issues:
const ExpandedComponentForWindowPortal = ({ externalWindow, ...rest }) => { const [countOfResize, setCountOfResize] = useState(0); useEffect(() => { const handleResize = () => { // Use functional update to avoid closure issues setCountOfResize(prevCount => prevCount + 1); }; if (externalWindow) { externalWindow.addEventListener("resize", handleResize); } return () => { if (externalWindow) { externalWindow.removeEventListener("resize", handleResize); } setCountOfResize(0); }; }, [externalWindow]); // Only re-run if externalWindow changes return ( <div> Resize count: {countOfResize} </div> ); };
How It Works
Children.mapiterates over each child element (supports multiple children if needed).cloneElementcreates a copy of each child, adding theexternalWindowprop with the reference to the new window instance.- The updated resize handler uses a functional update for
setCountOfResizeto ensure we always have the latest count value, and we properly guard againstexternalWindowbeing null.
内容的提问来源于stack exchange,提问作者Elli Zorro

