當下前端屆最火的技術之一莫過於React + Redux + webpack的技術結合。最近公司內部也正在轉react,這周主要做了個React的modal組件,接下來談下具體實現過程。
基本的HTML結構
雖然React基於虛擬DOM,但他的JSX語法還是離不開最基本的HTML。第一步要做的就是通過HTML&&CSS實現Dialog垂直水平居中框。HTML結構如下:
<div className="m-mask"></div>
<div className="m-dialog">
<div className="md-dialog">
<div className="md-dialog-title">
<h4>{title}</h4>
<span className="btn">
<i className="iconfont">×</i>
</span>
</div>
<div className="md-dialog-content">
{this.props.children}
</div>
<div className="md-dialog-foot">
<a href="#" className="btns">取消</a>
<a href="#" className="btns btns-blue">確定</a>
</div>
</div>
</div>
ps: JSX語法的className對應於HTML中的class,其次文中的iconfont圖標被換成了×。
然后寫下對應的CSS樣式。此處主要說明一下主要的樣式布局原理,細節略過。
Modal框的背景mask樣式通過position:fixed + top/right/bottom/left:0 + height: 100%實現。
不定寬高的主體內容水平垂直居中的實現通過position:fixed + top/left: 50% + translate(-50%, -50%)實現。
React Modal Component
有了已經想好的布局樣式,開始實現最基本的Modal組件。因為需要動態控制組件的顯隱,所以組件的顯隱在內部要通過state方便控制,而其他屬性則通過props實現。modal.js代碼如下:
import React, { Component, PropTypes } from 'react'
const defaultProps = {
show: false,
title: '',
zIndex: 1000,
onOk: () => {},
onCancel: () => {},
}
const propTypes = {
title: PropTypes.string,
zIndex: PropTypes.number,
onOk: PropTypes.func,
onCancel: PropTypes.func,
}
export default class Modal extends Component {
constructor(props) {
super(props)
this.state = {show: props.show}
}
render() {
return (
// JSX語法的HTML
);
}
}
Modal.defaultProps = defaultProps
Modal.propTypes = propTypes
不過顯隱內部通過state控制,但父組件還是需要通過props傳遞初始默認值。而且來回調用同一個modal時,父組件是通過props中的show屬性控制。內部的state還是第一次調用時傳入的props值。這樣無法導致及時控制顯隱。此時react的componentWillReceiveProps()出場,完美解決這個bug。
俗話說bug是解不完的,雖然上面的組件勉強可以正常使用,但是用於樣式通過絕對定位來做的,無形中導致了另外一個坑,如果Modal的父組件采用了相對或者絕對定位,即影響了Modal組件的定位,就會存在Modal出現在了某個div中,而不是理想的body中。bug復現如下:

unstable_renderSubtreeIntoContainer登場
為了保證我們的組件始終處於body中,采取了ReactDOM中的的這個不太正式的API。語法很簡單:
ReactDOM.unstable_renderSubtreeIntoContainer(parent, component, dom)
parent一般是this,component是Modal,dom是div
代碼實現如下:
export default class extends Component {
appendMaskIntoDoc() {
ReactDOM.unstable_renderSubtreeIntoContainer(
this,
<Modal {...this.props}>
{this.props.children}
</Modal>,
this.container
)
}
componentDidMount() {
this.container = document.createElement('div')
document.body.appendChild(this.container)
this.appendMaskIntoDoc()
}
componentDidUpdate() {
this.appendMaskIntoDoc()
}
componentWillUnmount() {
document.body.removeChild(this.container)
}
render() {
return null
}
}
API形式
此時,Modal組件已經成功做出來了。父組件可以成功調用,效果如下:

不過,偷偷see了下螞蟻金服官網的Modal組件調用,還有一種API形式的調用。於是也簡單實現了下。這里,簡單說下實現思路吧。
Confirm function內部通過setState方法函數接受的參數傳遞給Modal的父組件dialog,onOk的promise異步回調則是在dialog內部通過處理之后再傳遞給Modal組件。
此處我是通過正則表達式檢測new Promise,如果屬於Promise,則給onOk綁定then,內部調用setState控制Modal的隱藏。不過在調用Confirm function之前,
dialog組件已經被render進ReactDOM中,render之前則需要dom節點,就需要能獲取到document節點。然后手動創建空div節點添加到body中。
此處代碼有點長,省略咯。
