2
本文以Antd中的Modal组件作为案例剖析,重点讲述如何用Portals手动实现Modal组件。

Antd中Modal组件效果展示

(1)简易版Modal使用--代码演示
import React, { Component } from 'react'
import {Modal,Button} from 'antd'
class DialogPage extends Component {
  state={
    isShow:false,
  }
  toggle=()=>{
    this.setState({
      isShow:!this.state.isShow
    })
  }
  handleOk=()=>{
    this.setState({
      isShow:false
    })
  }
  handleCancel=()=>{
    this.setState({
      isShow:false
    })
  }

  render () {
    return (
      <div>
        <Button type={'primary'} onClick={this.toggle}>toggle</Button>
        <Modal
          title="Basic Modal"
          visible={this.state.isShow}
          onOk={this.handleOk}
          onCancel={this.handleCancel}
        >
          <p>Some contents...</p>
          <p>Some contents...</p>
          <p>Some contents...</p>
        </Modal>
      </div>
    )
  }
}

export default DialogPage
(2)效果预览

image.png

请注意:代码中Modal组件与Button平齐。当dom渲染完成后,Modal组件与根结点root平齐。
概述:
弹窗类组件的要求弹窗内容在A处声明,却在B处展示。react中相当于弹窗内容看起来被render到一个组件里面去,实际改变的是网页上另一处的DOM结构,这个显然不符合正常逻辑。但是通过使用框架提供的特定API创建组件实例并指定挂载目标仍可完成任务。

手动实现简易版Modal组件

Portals提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案,这能够帮助我们解答上面的疑问。

(1)自定义Modal组件

// Modal组件
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import './modal.css'
import {createPortal} from 'react-dom'

class Modal extends Component {

  constructor (props){
    super(props)
    this.state={
      node:undefined
    }
  }

  static getDerivedStateFromProps(props, state){
    const document=window.document
    if(props.visible){ // visible 为true时,body中新增div,为createPortal提供一个挂载节点。
      const node=document.createElement('div')
      document.body.appendChild(node)
      return {
        node // 将挂载的Dom节点存储起来,方便移除时使用
      };
    }
    if(state.node){ // visible为false时,移除对应的dom
      document.body.removeChild(state.node)
    }
    return null
  }

  render () {
    const {visible,title,onOk,onCancel}=this.props
    if(!visible){
      return null;
    }
    return createPortal( //第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container)是一个 DOM 元素。
      <div className='modalWrapper'>
        {
          title&&(
            <div className='modalTitle'>
              {title}
            </div>
          )
        }
        {this.props.children}
        <div className='modalFooter'>
          <button onClick={onCancel}>取消</button>
          <button onClick={onOk}>确认</button>
        </div>
      </div>,
      this.state.node
    )
  }
}
Modal.propTypes={
  visible:PropTypes.bool
}
Modal.defaultProps={
  visible:true
}
export default Modal
// 自定义Modal使用页面
import React, { Component } from 'react'
import Modal from './Modal'
class ModalPage extends Component {
  state={
    isShow:false,
    isShow2:false
  }
  toggle=()=>{
    this.setState({
      isShow:!this.state.isShow
    })
  }
  handleOk=()=>{
    this.setState({
      isShow:false
    })
  }
  handleCancel=()=>{
    this.setState({
      isShow:false
    })
  }
  render () {
    return (
      <div>
        <button onClick={this.toggle}>toggle</button>
        <Modal
          title="custom Modal"
          visible={this.state.isShow}
          onOk={this.handleOk}
          onCancel={this.handleCancel}
        >
          <p>自定义Modal</p>
          <p>自定义Modal</p>
        </Modal>
      </div>
    )
  }
}

export default ModalPage
.modalWrapper{
    position:absolute;
    z-index: 100;
    top:50%;
    left:50%;
    transform: translate(-50%,-50%);
    width:200px;
    border:1px solid red;
    text-align: center;
}
.modalTitle{
    border-bottom: 1px solid red;
}
.modalFooter{
    border-top: 1px solid red;
}

(2) 效果展示

image.png
如图所示,我们实现了Dom渲染穿越了层级。

(3) 概述

总的来说上面的Modal组件实现很容易,主要关注两点:
1.熟悉Portals的用法,可以看官方文档有更详细的解释。
2.关闭Modal时,同时移除对应Dom。代码中有对应的注释。


Francis
122 声望5 粉丝