在設計UI組件的過程當中不可避免的須要考慮模態窗的需求,好比dialog,tooltip這些,可是在React的框架下,咱們彷佛遇到了一些問題node
一般在設計這些模態窗的時候,會把整個DOM結構儘可能渲染在HTML位置比較頂層的地方,好比body。這樣相對來講樣式的自由度會比較高。 可是在React的總體框架下,它的數據流向是自上而下的,若是你的modal中的內容依賴父級的數據,那可能就要將對應的組建掛載在依賴組建裏面。固然能夠在頂層組件管理modal數據或者直接上Redux,可是這對於一個定位成UI組件的設計上來講,顯示不夠合理。這便促成了portal的想法出現---但願modal組件 能跟正常的組件同樣無論哪裏須要就在哪裏掛載,但實際DOM的位置確是另一個地方(好比React Bootstrap的Portal實現)app
首先組件不能渲染在它掛載的地方框架
render() {
return null;
}
複製代碼
DOM真正渲染的位置,經過renderLayer來實現ui
renderLayer() {
//這裏咱們假定render的執行是輸出渲染內容
const { render } = this.props;
//構造DOM節點做爲渲染內容的容器
if (!this.layer) {
this.layer = document.createElement('div');
document.body.appendChild(this.layer);
}
const layerElement = render();
this.layerElement = ReactDOM.unstable_renderSubtreeIntoContainer(this, layerElement, this.layer);
}
unrenderLayer() {
if (this.layer) {
React.unmountComponentAtNode(this.layer);
document.body.removeChild(this.layer);
this.layer = null;
}
}
複製代碼
好了,咱們在各個生命週期裏面調用它們就好了this
ReactDOM中提供了一個unstable_renderSubtreeIntoContainer,從名字上就能夠發現,它並不推薦被使用,實際上它也的確表現得使人費解。spa
class Test extends React.Component {
componentDidMount() {
console.log('test');
setTimeout(() => this.forceUpdate(),5000)
}
componentDidUpdate() {
console.log('did update test')
}
render() {
return <p>test<A/></p>;
}
}
class B extends React.Component {
componentDidMount() {
console.log('did mount B')
}
componentDidUpdate() {
console.log('did update B')
}
render() {
return <a>some thing</a>;
}
}
class A extends React.Component {
componentDidMount() {
this.renderLayer();
console.log('did mount A')
}
componentDidUpdate() {
this.renderLayer();
console.log('did update A')
}
renderLayer() {
if (!this.layer) {
this.layer = document.createElement('div');
document.body.appendChild(this.layer);
}
ReactDOM.unstable_renderSubtreeIntoContainer(this, <B/>, this.layer);
}
render() {
return null;
}
}
ReactDOM.render(<Test />, document.getElementById('app')); 複製代碼
按咱們對React父子組件間生命週期的執行狀況上理解應當輸出 https://codepen.io/anon/pen/GQRaEo?editors=1112設計
"did mount B"
"did mount A"
"test"
"did update B"
"did update A"
"did update test"
複製代碼
而實際的結果倒是code
"did mount B"
"did mount A"
"test"
"did update A"
"did update test"
"did update B"
複製代碼
顯然在初始化的時候事情仍是符合咱們預期的 但是在執行更新組件的時候,生命週期的執行便顯得很混亂,在React16的版本中這個問題獲得了修復,但執行的結果顯然也不是咱們最終想要的 https://codepen.io/anon/pen/MQWdPq?editors=1111component
"did mount A"
"test"
"did mount B"
"did update A"
"did update test"
"did update B"
複製代碼
React Portal的出現完全解決了這方面的問題xml
終於進入主題,先看看它是如何使用的
const node = document.createElement('div');
document.body.appendChild(this.node);
...
render() {
return createPortal(
<div class="dialog"> {this.props.children} </div>, //須要渲染的內容
node //渲染內容的容器DOM
);
}
複製代碼
除了node節點在一些場景下須要釋放以外,你已經不須要在其餘生命週期裏面擦屁股了 讓咱們在回到以前生命週期執行上的問題 https://codepen.io/anon/pen/Jpjqwg?editors=1111 結果的執行跟咱們正常組件保持了一致,不再用擔憂一些依賴子組件完成更新後的監聽或操做會出現異常狀況了。 除此以外React Portal還新增了一個事件冒泡的實現
<div onClick={handleClick}>
<Dialog/>
</div>
複製代碼
若是在React16以前的實現方式,點擊Dialog組件裏面的內容handleClick是不會被觸發,但經過React Portal實現的掛載方式將會發生冒泡。 這個特性見仁見智吧,通常狀況下感受也不會用到。
總之React Portal的實現對於modal的實現是一個重大的更新,同時也避免了組件間生命週期的執行混亂。