讀zent源碼庫之Dialog組件實現

一、Dialog組件提供什麼功能,解決什麼問題?

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);
  1. 能夠經過visible屬性控制彈層的顯示與隱藏
  2. 能夠隨意的在Dialog組件裏添加任意多的內容
  3. 能夠在任意位置使用Dialog組件

二、若是我來實現會怎麼作?

若是我來實現,其實很簡單,用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的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根節點外面去並不容易。

(全文完)

相關文章
相關標籤/搜索