javascript - 由于 React Portals 和 componentDidMount 导致的 ref 问题

标签 javascript reactjs

背景:
我正在尝试找出实现包装 React 的 native 门户实用程序的门户组件的最佳方法。该组件将简单地处理创建门户的根元素,将其安全地插入到 DOM 中,将组件的任何子元素渲染到其中,然后在卸载组件时再次将其从 DOM 中安全地移除。

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

问题编号 1:
如果 Portal 组件'portals'在它的 render 方法期间它是创建的根元素的子元素,但在将根元素附加到 DOM 树之前等到它的 componentDidMount 方法被触发,那么任何门户的子元素在它们的过程中需要访问 DOM自己的 componentDidMount 生命周期方法会有问题,因为在那个时间点它们将被挂载到一个分离的节点。 这issue后来在 React 的文档中得到解决,该文档建议在 Portal 组件完成安装并成功将门户根元素附加到 DOM 树后,将 Portal 组件状态的“mounted”属性设置为 true。然后在渲染中,您可以推迟渲染 Portal 的任何子级,直到 mounted 属性设置为 true,因为这将保证所有这些子级在它们各自的 componentDidMount 生命周期方法之前被渲染到实际的 DOM 树中会开火。伟大的。但这导致我们...

问题编号 2:
如果您的 Portal 组件在它自己挂载之后才渲染它的任何子组件,那么它的祖先的任何 componentDidMount 生命周期方法也将在任何这些子组件被挂载之前触发。因此,任何需要在其自己的 componentDidMount 生命周期方法期间访问任何这些子项的 refs 的 Portal 组件的祖先都会遇到问题。我还没有找到解决这个问题的好方法。

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

引用代码:

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

    }

}

最佳答案

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

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
}

关于javascript - 由于 React Portals 和 componentDidMount 导致的 ref 问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54540638/

相关文章:

javascript - JS-JSON : adding a keypair in each object by calculating the difference in time now and a timestamp in the JSON object

javascript - Angularjs 无法找到我的 Controller

javascript - 使用 emscripten C/C++ 到 Javascript LLVM 编译器的 RTMP 视频

javascript - 为什么我需要 setTimeout 来更新 React 组件中的状态

javascript - 我可以在 ng-bind 中创建 JavaScript 对象吗?

javascript - 在 HTML/javascript 中获取设备的唯一 token

javascript - 巴别塔 : Function parameter types in ES6

javascript - 使用 React 和 Material UI 创建动态标题 UI

javascript - React 将 prop 传递给其他组件以从状态数组中删除

javascript - 如何在 Next.js 中进行依赖的 fetch API 调用