zent的Dialog組件,使用姿式是這樣的(代碼摘自zent官方文檔:https://www.youzanyun.com/zan...)react
import { Dialog, Button } from 'zent'; class Example extends React.Component { state = { visible: false } triggerDialog = visible => { this.setState({ visible }); }; render() { return ( <div> <Button type="primary" onClick={() => this.triggerDialog(true)} > 顯示 </Button> <Dialog visible={this.state.visible} onClose={() => this.triggerDialog(false)} title="對話框" > <p>對話框內容</p> <p>對話框其餘內容</p> </Dialog> </div> ); } } ReactDOM.render(<Example />, mountNode);
若是我來實現,其實很簡單,用jQuery已經寫了無數遍了,可是,想一想當初用jQuery來寫組件的時候,很是方便的能夠在body下面插入一個div,而且綁定事件,或者,直接把彈層的div寫到body標籤下面。app
可是,react,倒是組件嵌套組件,全部組件都渲染在一個被稱爲root的div下面,這可怎麼把一個節點掛載到body下面呢?dom
也簡單,我直接用document.body.appendChild把組件直接插入到body後面,可是發現,document.body.appendChild的參數必須是標準的dom節點,而react裏面,經過this.props.children取出來的數據,並非標準的dom節點,這也好辦,把這些節點轉成標準的dom節點,而後插入到body下面不就ok了?我是這麼想的。可是,最後一步,事件怎麼綁定呢?這塊沒有深刻研究了,不過我想,應該這樣去實現也是沒有問題的。函數
順便說一下,曾經我還實現過一個React的彈層,可是必須放到最外層使用。。。哈哈,不說了,都是淚,侵入太強,沒法作到在任意位置使用Dialog組件。動畫
zent實現方式,其實跟我上面說的基本思路是一致的,只是,用的方法再也不是document.body.appendChild,而是用到了一個React提供的叫作ReactDOM.unstable_renderSubtreeIntoContainer(parentComponent, element, containerNode, callback)的方法,第一個參數是當前dialog所在的父組件,第二個參數是將要渲染的dialog內容,其實就是this.props.children,第三個參數是即將要掛載到body下面的一個div容器節點,callback就是成功以後的回調了。ui
從函數名能夠看出,這個方法是不穩定的,可是,zent仍是用了,不過效果很好,哈哈。this
zent的具體作法是,把ReactDOM.unstable_renderSubtreeIntoContainer方法放到了一個叫作Portal的組件上去實現這個功能,而後再把dialog內容放進這個Portal組件。code
<PortalComponent visible={visible} onClose={this.onClose} className={`${prefix}-dialog-r-anchor`} > <DialogEl {...this.props} onClose={this.onClose} style={elStyle}> {this.props.children} </DialogEl> </PortalComponent>
Portal組件自己的功能很是簡單,僅僅只是負責把其子組件渲染到body下面的一個新建立的div下面去。其餘的邏輯(好比顯示、隱藏、mask之類),所有都放到dialog組件自身上去實現。固然,顯示與隱藏的功能,仍是由Portal來控制,隱藏的時候,Portal會把當前的子組件從body上面卸載掉。component
如下是一個極簡的Portal實現,即把本身的子組件渲染到body上面去。事件
import React from 'react'; import ReactDOM from 'react-dom'; export default class Portal extends React.Component { renderChildren() { const container = document.createElement('div'); document.body.appendChild(container); ReactDOM.unstable_renderSubtreeIntoContainer(this, React.Children.only(this.props.children), container); } componentDidMount() { this.renderChildren(); } render() { return null; } }
如下是,基於Portal的Dialog組件的極簡實現:
import React from 'react'; import Portal from './portal'; export default class Dialog extends React.Component { render() { return <Portal> <div>{this.props.children}</div> </Portal> } }
使用方式:
import React, { Component } from 'react'; import ReactDOM, { render } from 'react-dom'; import Dialog from './dialog'; class App extends Component { onTextClick = () => { console.log('clicked') } render() { return ( <div> <Dialog > <div onClick={this.onTextClick}>this is dialog</div> </Dialog> </div> ); } } render(<App />, document.getElementById('root'));
固然,真正要實現一個Dialog須要考慮的問題還有不少,好比控制隱藏與顯示、定製動畫,自定義樣式,配置dialog的標題和其餘屬性等等,只是這些咱們在jQuery時代都已經作過。
這裏主要分析了Portal的實現,畢竟,在React裏面,要把組件渲染到app根節點外面去並不容易。
(全文完)