单击 react 在新窗口中打开一个组件

IT技术 javascript reactjs
2021-03-28 00:55:10

我有一个显示数据的组件。单击父组件中的按钮/链接时,我必须在新窗口中打开此组件。

export default class Parent extends Component {
    construtor(props) {
        super(props);
    }

    viewData = () => {
        window.open('childcomponent.js','Data','height=250,width=250');
    }

    render() {
        return (
            <div> <a onclick={this.viewData}>View Data</a></div>
        )
    }
}

我不知道如何调用另一个组件并将其显示在指定大小的新窗口中。

实际上,我需要向该子组件发送一个props,它将从数据库中获取数据并呈现它。

6个回答

您可以使用ReactDOM.createPortal在新窗口中渲染组件,正如David Gilbertson在他的帖子中所解释

class MyWindowPortal extends React.PureComponent {
  constructor(props) {
    super(props);
    // STEP 1: create a container <div>
    this.containerEl = document.createElement('div');
    this.externalWindow = null;
  }

  render() {
    // STEP 2: append props.children to the container <div> that isn't mounted anywhere yet
    return ReactDOM.createPortal(this.props.children, this.containerEl);
  }

  componentDidMount() {
    // STEP 3: open a new browser window and store a reference to it
    this.externalWindow = window.open('', '', 'width=600,height=400,left=200,top=200');

    // STEP 4: append the container <div> (that has props.children appended to it) to the body of the new window
    this.externalWindow.document.body.appendChild(this.containerEl);
  }

  componentWillUnmount() {
    // STEP 5: This will fire when this.state.showWindowPortal in the parent component becomes false
    // So we tidy up by closing the window
    this.externalWindow.close();
  }
}
样式存在问题,因为这些样式并未应用于新窗口中的所有组件。可能与新 dom 的头部现在是空的事实有关,这意味着当 React 应用程序的头部完全充满样式和脚本时,同一个 React 应用程序正在填充/更新。
2021-05-29 00:55:10
这个解决方案很好地包含在一个 npm module中:npmjs.com/package/react-new-window
2021-06-22 00:55:10

此答案基于David Gilbertson 的帖子它已被修改为在 Edge 中工作。为了使这项工作在边缘divstyle元件必须与为他们将要呈现的窗口中创建

class MyWindowPortal extends React.PureComponent {
  constructor(props) {
    super(props);
    this.containerEl = null;
    this.externalWindow = null;
  }

  componentDidMount() {
    // STEP 1: Create a new window, a div, and append it to the window. The div 
    // *MUST** be created by the window it is to be appended to (Edge only)
    this.externalWindow = window.open('', '', 'width=600,height=400,left=200,top=200');
    this.containerEl = this.externalWindow.document.createElement('div');
    this.externalWindow.document.body.appendChild(this.containerEl);
  }

  componentWillUnmount() {
    // STEP 2: This will fire when this.state.showWindowPortal in the parent component
    // becomes false so we tidy up by just closing the window
    this.externalWindow.close();
  }

  render() {
    // STEP 3: The first render occurs before componentDidMount (where we open the
    // new window) so container may be null, in this case render nothing.
    if (!this.containerEl) {
      return null;
    } 

    // STEP 4: Append props.children to the container <div> in the new window
    return ReactDOM.createPortal(this.props.children, this.containerEl);  
  }
}

完整的修改源可以在这里找到https://codepen.io/iamrewt/pen/WYbPWN

我可以使用它在弹出窗口中加载反应组件。但风格并没有发扬光大。弹出窗口中加载的组件不包含应用程序中定义的样式。有没有办法将样式传递到弹出窗口中?
2021-06-01 00:55:10
我尝试了上面的代码示例步骤。它仍然不适合我使用 IE 边缘。还有其他解决方案吗?
2021-06-13 00:55:10
我在窗口安装后第二次渲染时遇到了麻烦。除了当 this.containerEl 为空时,没有任何东西告诉它重新渲染。为了解决这个问题,我在 componentDidMount 的末尾添加了一个 setState({mopunted:true})。
2021-06-18 00:55:10

赞成的答案效果很好!

只是在这里留下一个功能组件版本,以防人们将来搜索它。

const RenderInWindow = (props) => {
  const [container, setContainer] = useState(null);
  const newWindow = useRef(null);

  useEffect(() => {
    // Create container element on client-side
    setContainer(document.createElement("div"));
  }, []);

  useEffect(() => {
    // When container is ready
    if (container) {
      // Create window
      newWindow.current = window.open(
        "",
        "",
        "width=600,height=400,left=200,top=200"
      );
      // Append container
      newWindow.current.document.body.appendChild(container);

      // Save reference to window for cleanup
      const curWindow = newWindow.current;

      // Return cleanup function
      return () => curWindow.close();
    }
  }, [container]);

  return container && createPortal(props.children, container);
};
@amiregelz 很难猜到没有看到 - 但我确实了解到,如果您正在执行 SSR,有时document.createElement服务器上将不可用,并且可能会失败。在这种情况下,您可以在useEffect. 不知道这是否是你的情况
2021-05-25 00:55:10
@Rahul 看到这里
2021-06-06 00:55:10
@rob-gordon 知道为什么在我的 create-react-app 生产版本中不起作用吗?
2021-06-13 00:55:10
我们如何承载应用程序样式和数据。
2021-06-18 00:55:10

您不会直接打开组件。您需要一个新的页面/视图来显示组件。当您打开窗口时,您会将其指向适当的 URL。

至于大小,您在 open 的第三个参数中将其作为字符串提供,您实际上是正确的:

window.open('http://example.com/child-path','Data','height=250,width=250');

但是请注意,浏览器可能出于各种原因不尊重您的宽度和高度请求。出于这个原因,最好同时应用适当的 CSS 以获得正确大小的空间,以防它打开比您想要的大。

添加到当前答案 - 要将样式从原始窗口复制到弹出窗口,您可以:

function copyStyles(src, dest) {
    Array.from(src.styleSheets).forEach(styleSheet => {
        dest.head.appendChild(styleSheet.ownerNode.cloneNode(true))
    })
    Array.from(src.fonts).forEach(font => dest.fonts.add(font))
}

然后添加

copyStyles(window.document, popupWindow.document);

在调用之后window.open并且一旦 ref 被初始化