原文地址 小寒的博客react
這裏的彈窗泛指全部的彈出組件,這些組件不受頁面其餘UI佈局影響,處於DOM結構的頂層,絕對定位在body元素下。算法
這個特殊性也給它的開發提出了特殊的要求。api
react新版本中的createPortal Api能夠很方便的製造一個組件到制定的dom裏。app
在componentDidMount中進行ReactDOM.render方法是一個很巧妙的技巧。dom
話很少說,開始貼代碼佈局
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的時候,卸載彈窗this
而更新屬性也是經過從新render去渲染的,因爲react偉大的diff算法,咱們即便ReactDOM.render從新渲染也不會致使頁面刷新,而只是屬性的變化引發的頁面變更spa
2. createElementToModalRoot方法code
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的過程