【问题标题】:Open a component in new window on a click in react单击反应在新窗口中打开组件
【发布时间】:2017-11-30 13:20:17
【问题描述】:

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

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>
        )
    }
}

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

实际上我需要向该子组件发送一个道具,它会使用它从数据库中获取我的数据并呈现它。

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    您可以使用ReactDOM.createPortal 在新窗口中呈现组件,正如David Gilbertson 在他的post 中解释的那样:

    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();
      }
    }
    

    【讨论】:

    • 这个解决方案很好地封装在一个 npm 模块中:npmjs.com/package/react-new-window
    • 样式有问题,因为这些样式并未应用于新窗口中的所有组件。可能与新 dom 的头部现在是空的这一事实有关,这意味着当反应应用程序的头部完全充满了样式和脚本时,同一个反应应用程序正在填充/更新。
    【解决方案2】:

    赞成的答案效果很好!

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

    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);
    };
    

    【讨论】:

    • 我们如何携带应用程序样式和数据。
    • @Rahul 请参阅here
    • @rob-gordon 知道为什么 this 在我的 create-react-app 生产版本中不起作用吗?
    • @amiregelz 很难猜测没有看到 - 但我确实了解到,如果你正在执行 SSR,有时document.createElement 将在服务器上不可用并且它可能会失败。在这种情况下,您可以在useEffect 中创建 div。不知道这是否是您的情况
    【解决方案3】:

    此答案基于David Gilbertson's post。它已被修改为在 Edge 中工作。要在 Edge 中进行这项工作,divstyle 元素必须是 created with the window into which they will be rendered

    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

    【讨论】:

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

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

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

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

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

    【讨论】:

      【解决方案5】:

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

      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 被初始化

      【讨论】:

        【解决方案6】:

        如果有人在将样式添加到新窗口时遇到问题,诀窍是将整个 DOM 头部从父窗口复制到新的弹出窗口。 首先你需要建立整个 html 骨架

        newWindow.document.write("<!DOCTYPE html");
        newWindow.document.write("<html>");
        newWindow.document.write("<head>");
        newWindow.document.write("</head>");
        newWindow.document.write("<body>");
        newWindow.document.write("</body>");
        newWindow.document.write("</html>");
        // Append the new container to the body of the new window
        newWindow.document.body.appendChild(container);
        

        现在在新窗口的 DOM 中我们有一个空的 head 标签,我们遍历父 head 标签并将其子标签附加到新的 head。

        const parentHead= window.document.querySelector("head").childNodes;
        parentHead.forEach( item =>{
            newWindow.document.head.appendChild(item.cloneNode(true)); // deep copy
        })
        

        仅此而已。新窗口与父窗口具有相同的头子窗口,您的所有样式现在都应该可以工作了。

        附:有些样式可能有点顽固并且不起作用,我发现对这些样式有效的技巧是在正确的时间在 componentDidMount 或 useEffect 中添加 setTimeout ,以便您可以使用父头更新新头。像这样的

        setTimeout(() => {
          updateHead();
        }, 5000);
        

        【讨论】:

          猜你喜欢
          • 2018-01-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-12-24
          • 2011-02-09
          • 2015-09-09
          • 2020-08-12
          相关资源
          最近更新 更多