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的過程