当前位置: 首页 > 知识库问答 >
问题:

React Portals和组件引起的Ref问题

俞飞鸣
2023-03-14

背景:
我正试图找出实现封装React的本机门户实用程序的门户组件的最佳方法。组件只需创建门户的根元素,安全地将其插入DOM,将组件的任何子元素呈现到其中,然后在组件卸载时安全地将其再次从DOM中移除。

问题:
React强烈建议避免React安全生命周期方法(componentDidMount、componentDidUpdate等)之外的副作用(如操作DOM)因为这有可能导致问题(内存泄漏、过时节点等)。在React关于如何使用门户的示例中,他们将门户的根元素装载到componentDidMount上的DOM树中,但这似乎会导致其他问题。

问题1:
如果门户组件在其render方法期间将其子元素“portals”添加到已创建的根元素中,但在将该根元素添加到DOM树之前,等待其componentDidMount方法激发,然后,门户的任何子级在其自己的componentDidMount生命周期方法中需要访问DOM时都会出现问题,因为此时它们将被装载到分离的节点上。这个问题后来在React的文档中得到了解决,该文档建议在门户组件完成装载并成功地将门户根元素附加到DOM树后,在门户组件的状态中将“mounted”属性设置为true。然后在渲染中,您可以推迟渲染门户的任何子级,直到挂载属性设置为true,因为这将保证所有这些子级都将在各自的组件挂载生命周期方法触发之前渲染到实际的DOM树中。伟大的但这让我们想到。。。

问题2:
如果你的Portal组件推迟渲染它的任何子组件,直到它自己已经挂载,那么它的祖先的任何组件didMount生命周期方法也将在挂载任何这些子组件之前启动。因此,Portal组件的任何祖先在其自己的组件中需要访问这些子组件中的任何引用。我还没有想出绕过这个的好方法。

问:
是否有一种干净的方法来安全地实现门户组件,以便它的子组件在其组件的生命周期方法中可以访问DOM,同时也允许门户组件的祖先在其各自的组件中访问这些子组件的引用。生命周期方法?

参考代码

import { Component } from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';


export default class Portal extends Component {

    static propTypes = {

        /** This component uses Portals to dynamically render it's contents into
        *   whatever DOM Node contains the **id** supplied by this prop
        *   ('portal-root' by default). If a DOM Node cannot be found with the
        *   specified **id** then this component will create one and append it
        *   to the 'Document.Body'. */
        rootId: PropTypes.string

    };

    static defaultProps = {
        rootId: 'portal-root'
    };

    constructor(props) {
        super(props);
        this.state = { mounted: false };
        this.portal = document.createElement('div');
    }

    componentDidMount() {
        this.setRoot();
        this.setState({ mounted: true });
    }

    componentDidUpdate( prevProps, prevState ) {
        if( this.props.rootId !== prevProps.rootId ) this.setRoot();
    }

    componentWillUnmount() {
        if( this.root ) {
            this.root.removeChild(this.portal);
            if( !this.root.hasChildNodes() ) this.root.parentNode.removeChild(this.root);
        }
    }

    render() {

        this.portal.className = this.props.className ? `${this.props.className} Portal` : 'Portal';

        return this.state.mounted && ReactDOM.createPortal(
            this.props.children,
            this.portal,
        );
    }

    setRoot = () => {

        this.prevRoot = this.root;
        this.root = document.getElementById(this.props.rootId);

        if(!this.root) {
            this.root = document.createElement('main');
            this.root.id = this.props.rootId;
            document.body.appendChild(this.root);
        }

        this.root.appendChild(this.portal);

        if( this.prevRoot && !this.prevRoot.hasChildNodes() ) {
            this.prevRoot.parentNode.removeChild(this.prevRoot);
        }

    }

}

共有1个答案

柳梓
2023-03-14

构造函数是一种有效的生命周期方法,您可以在其中执行副作用。没有理由不能在构造函数中创建/附加根元素:

class Portal extends Component {

  constructor(props) {
     super();
     const root = document.findElementById(props.rootId);
     this.portal = document.createElement('div');
     root.appendChild(portal);
  }

  componentWillUnmount() {
     this.portal.parent.removeChild(this.portal);
  }

  render() {
     ReactDOM.createPortal(this.props.children, this.portal);
  }

  // TODO: add your logic to support changing rootId if you *really* need it
}
 类似资料:
  • 我正在用vue3 开发一个大屏项目,大屏背景是unity开发的,我需要和背景进行交互,例如场景切换、互发消息传递数据等,因为多个页面都是使用同一个背景组件,我不希望切换页面时重新加载Unity,所以Unity全局公共加载。 我希望能在每个页面能够获取到Unity组件的Ref,通过Ref调用Unity组件中的发送消息、切换场景的事件,有什么思路? 我现在在考虑能不能将UnityRef挂载到全局去使用

  • 代码demo: 请教一下子组件的watch不加()=>就无法进入监听?

  • 我在使用带有样式化组件的s时遇到困难。当我尝试在下面的类方法中访问它们时,会出现以下错误: Edit.js:42未捕获类型错误:此…..包含的不是函数 我从这个问题中找到了密码 我到底做错了什么?

  • 主要内容:本节引言:,1.什么是OOM?为什么会引起OOM?,2.避免Bitmap引起的OOM技巧小结,2)图片压缩,3.及时回收图像,4.其他方法,本节小结:本节引言: 上节,我们已经学习了Bitmap的基本用法,而本节我们要来探讨的Bitmap的OOM问题, 大家在实际开发中可能遇到过,或者没遇到过因为Bitmap引起的OOM问题,本节我们 就来围绕这个话题来进行学习~了解什么是OOM,为什么会引起OOM,改善因Bitmap引起的 OOM问题~ 1.什么是OOM?为什么会引起OOM? 答:O

  • 本文向大家介绍VS2013的Browser Link引起的问题,包括了VS2013的Browser Link引起的问题的使用技巧和注意事项,需要的朋友参考一下 环境:vs2013 问题:在调用一个WebApi的时候出现了错误: 于是我用Fiddler 4直接调用这个WebApi,状态码是200(正常的),JSon里却提示在位置9409处文本非法, 以TextView方式查看,在9409处发现了Br

  • 问题内容: 如果我有类似的东西 我想从我拥有的地方访问,该怎么办? 问题答案: 推荐用于16.3之前的React版本 如果无法避免,那么从React文档中提取的建议模式将是: 该 家长 转发绑定到一个功能道具 家长 。当呼叫作出反应的 儿童的 道具也将分配 孩子的 给 父母的 财产。 建议用于React> = 16.3 引用转发是一种选择加入功能,它使某些组件可以接收它们收到的引用,并将其进一步向