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

如何在React的createPortal函数中向子组件传递Props——以WindowPortal传递窗口实例处理resize事件为例

How to Pass Props to 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.map iterates over each child element (supports multiple children if needed).
  • cloneElement creates a copy of each child, adding the externalWindow prop with the reference to the new window instance.
  • The updated resize handler uses a functional update for setCountOfResize to ensure we always have the latest count value, and we properly guard against externalWindow being null.

内容的提问来源于stack exchange,提问作者Elli Zorro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 15:08:13