1. 程式人生 > >React彈窗元件

React彈窗元件

原文地址 小寒的部落格

這裡的彈窗泛指所有的彈出元件,這些元件不受頁面其他UI佈局影響,處於DOM結構的頂層,絕對定位在body元素下。

 

這個特殊性也給它的開發提出了特殊的要求。

 

react新版本中的createPortal Api可以很方便的製造一個元件到制定的dom裡。

在componentDidMount中進行ReactDOM.render方法是一個很巧妙的技巧。

 

話不多說,開始貼程式碼

 

1. 在componentDidMount去渲染dom

class Modal extends React.Component {
  el 
= null componentDidMount() { this.el = createElementToModalRoot() renderModal(this.el, this.props) } componentDidUpdate() { renderModal(this.el, this.props) if (!this.props.visible) { unRenderModal(this.el) } } componentWillUnMount() { unRenderModal(this.el) } render() {
return null } }

 

上邊的程式碼就是彈窗的核心思想,首先建立element在root元素下,然後渲染dom,在unmount或者visible屬性為false的時候,解除安裝彈窗

而更新屬性也是通過重新render去渲染的,由於react偉大的diff演算法,我們即使ReactDOM.render重新渲染也不會導致頁面重新整理,而只是屬性的變化引起的頁面變動

 

2. createElementToModalRoot方法

function createElementToModalRoot() {
  let modalRoot

  modalRoot 
= document.getElementById('modal-root') if (!modalRoot) { modalRoot = document.createElement('div') modalRoot.setAttribute('id', 'modal-root') const modalMask = document.createElement('div') modalMask.setAttribute('id', 'modal-mask') document.body.appendChild(modalRoot) modalRoot.appendChild(modalMask) } const el = document.createElement('div') modalRoot.appendChild(el); return el }

 

用dom方法建立元素,因為此時componentDidMount所以我們可以肆無忌憚的進行dom操作了,執行這個方法之後我們會建立#modal-root #modal-mask 以及 待會render的dom元素

 

3. renderModal方法

const renderModal = (el, props) => {
  const modalRoot = document.getElementById('modal-root')
  const modalMask = document.getElementById('modal-mask')

  modalMask.style.display = 'block'
  modalRoot.style.display = 'block'

  ReactDOM.render(<ModalInner {...props} />, el)
}

 

上面添的程式碼我們用ModalInner元件建立了一個去渲染了新增在#modal-root下面的dom,每次更新元件,也是通過他再次渲染

 

4. ModalInner元件

class ModalInner extends React.Component {
  render() {
    const { children, title, visible, onCancel, onOk } = this.props

    return (
      <div className={classnames('modal', visible ? 'modal-animate-in' : 'modal-animate-out')}>
        <div className="modal-head">
          <div className="modal-title">{title}</div>
          <div className="modal-cancel-btn" onClick={onCancel}>+</div>
        </div>
        <div className="modal-content">
          {children}
        </div>
        <div className="modal-footer">
          <button className="do-btn" onClick={onCancel}>取消</button>
          <button className="do-btn do-btn-primary" onClick={onOk}>確定</button>
        </div>
      </div>
    )
  }
}

 

這個元件,我們設定了最常用的一些屬性,包括title children visible 和 onCancel onOk

 

5. unRenderModal方法

最後我們就剩下解除安裝方法了

const unRenderModal = (el) => {
  const modalRoot = document.getElementById('modal-root')
  const modalMask = document.getElementById('modal-mask')

  modalMask.style.display = 'none'
  modalRoot.style.display = 'none'

  modalRoot.removeChild(el);
}

 

 

 

6. 新增動畫上邊的ModalInner元件裡可以看到他會根據visible對dom新增不同的animate從而產生動畫

但是如果unRenderModal方法會直接移除dom,所以不會產生移除動畫

 

所以我們把上邊的componentDidMount修改一下

componentDidUpdate() {
    renderModal(this.el, this.props)

    if (!this.props.visible) {
      setTimeout(() => unRenderModal(this.el), 500)
    }
  }

 

 

7. Modal.open方法

Modal.open = option => {
  const props = {...option}
  const el = createElementToModalRoot()
  const close = () => {
    option.visible = false
    renderModal(el, option)
    setTimeout(() => unRenderModal(el), 500)
  }

  props.visible = true
  props.children = option.content
  props.onOk = e => {
    option.onOk ? option.onOk(e, close) : close()
  }
  props.onCancel = () => {
    option.ononCancel ? option.ononCancel(e, close) : close()
  }

  renderModal(el, props)
}

 

還是用的上面的那些api,這是visible屬性是我們手動傳入元件裡的

這樣我們就可以通過非api的形式去開啟一個彈窗了

 

 

以上便是render方法建立彈窗的方式,當然很推薦使用createPortal方法,可以省去手動render和unRender的過程