React如何将组件渲染到指定节点—ReactDOM.createPortal

适用场景

在React开发过程中,会遇到弹窗、全局组件等开发的情况,这时候我们可能根据不同的需求需要把组件渲染至指定页面,通过会把组件渲染到body节点下。我们可以使用ReactDOM的createPortal方法完成这一需求。

ReactDOM.createPortal

先来看下react-dom库里面关于createPortal的源码

function createPortal(
  children: ReactNodeList,
  container: Container,
  key: ?string = null,
): React$Portal {
  invariant(
    isValidContainer(container),
    'Target container is not a DOM element.',
  );
  // TODO: pass ReactDOM portal implementation as third argument
  // $FlowFixMe The Flow type is opaque but there's no way to actually create it.
  return createPortalImpl(children, container, null, key);
}

createPortal函数主要有三个参数,分别是children(需要渲染的组件)、container(需要渲染到的指定节点)、key。开发中我们主要关注children和container即可。

Usage

支持createPortal方法的版本

import React from 'react';
import ReactDOM from 'react-dom';
 
/**
 * @function getContainer 渲染组件的父组件
 * @param children 需要渲染的组件
 * @export
 * @class Portal
 * @extends {React.Component}
 */
export default class Portal extends React.Component {
  componentDidMount() {
    this.createContainer();
  }
 
 componentDidUpdate(prevProps) {
    const { didUpdate } = this.props;
    if (didUpdate) {
      didUpdate(prevProps);
    }
  }
 
  componentWillUnmount() {
    this.removeContainer();
  }
 
  createContainer() {
    this._container = this.props.getContainer();
    this.forceUpdate();
  }
 
  removeContainer() {
    if (this._container) {
      this._container.parentNode.removeChild(this._container);
    }
  }
 
  render() {
  if (this._container) {
      ReactDOM.createPortal(this.props.children, this._container);
    }
    return null;
  }
}

getContainer示例

const getContainer = (domId = 'c-modal') => {
  const domContainer = document.createElement('div');
  domContainer.id = domId;
  domContainer.style.position = 'absolute';
  domContainer.style.top = '0';
  domContainer.style.left = '0';
  domContainer.style.width = '100%';
  document.getElementsByTagName('body')[0].appendChild(domContainer);
  return domContainer;
}

Portal组件示例

<Portal getContainer={() => this.getContainer('c-id')}>需要渲染的组件</Portal>

react-dom版本较低不支持createPortal时

import React from 'react';
import ReactDOM from 'react-dom';
 
/**
 * @function getContainer 渲染组件的父组件
 * @param children 需要渲染的组件
 * @export
 * @class Portal
 * @extends {React.Component}
 */
export default class Portal extends React.Component {
  componentDidMount() {
    this.createContainer();
  }
 
  componentDidUpdate() {
    // React版本较低,不使用ReactDOM.createPortal
    ReactDOM.unstable_renderSubtreeIntoContainer(
      this,
      this.props.children,
      this._container,
    );
  }
 
  componentWillUnmount() {
    this.removeContainer();
  }
 
  createContainer() {
    this._container = this.props.getContainer();
    this.forceUpdate();
  }
 
  removeContainer() {
    if (this._container) {
      this._container.parentNode.removeChild(this._container);
    }
  }
 
  render() {
    return null;
  }
}

虽然低版本也有不同的解决方案,但是尽量还是升级选择使用ReactDOM.createPortal

Author: Liquorxm Created: Jul 30, 2020 6:09 PM Tags: FrontEnd, React, ReactDOM, createPortal