antd源碼解讀(8.1)- 番外篇 Trigger 之 index.js 頭部

題外話

啊哈,最近週末偷了下懶,去看LPL半決賽了,雖然中國隊伍沒有進入決賽,可是仍是打心底支持中國隊,但願他們可以在將來取得更好的成績。html

Trigger

這個組件的index文件就有不少代碼,590行代碼,並且在頭部引入的額外文件特別的多,因此咱們這一個組件就先從這些額外的組件中開始吧,先看看這些外部方法可以作些什麼。node

// index.js 頭部
  import PropTypes from 'prop-types';
  import { findDOMNode, createPortal } from 'react-dom';
  import createReactClass from 'create-react-class';
  import contains from 'rc-util/lib/Dom/contains';
  import addEventListener from 'rc-util/lib/Dom/addEventListener';
  import Popup from './Popup';
  import { getAlignFromPlacement, getPopupClassNameFromAlign } from './utils';
  import getContainerRenderMixin from 'rc-util/lib/getContainerRenderMixin';
  import Portal from 'rc-util/lib/Portal';複製代碼

createPortal

在官網這裏有這麼一個解釋react

ReactDOM.createPortal(child, container)複製代碼

Creates a portal. Portals provide a way to render children into a DOM node that exists outside the hierarchy of the DOM component.es6

這個函數是用來建立一個portal,而這個Portal是提供一個方法來在指定的dom元素渲染一些組件的方法。api

createReactClass

這個函數也是可以在官網這裏上找到的,是用來建立一個raect類而不是用es6語法的方法,在裏面可使用getDefaultProps()方法app

建立當前組件的默認props,可使用getInitialState()建立當前組件的初始state,而且在裏面寫的方法都會自動的綁定上this,dom

也就是他所說的Autobinding,還有一個最有用的屬性Mixins,這個是可以在編寫不少的可以使用的外部方法傳入組件的屬性。ide

contains && addEventListener

這兩個函數都是rc-util/lib/Dom/裏面的工具函數,接下來咱們分辨看看這兩個函數可以作啥函數

// contains.js

// 這個函數是用來判斷傳入根節點root是否包含傳入節點n,
// 若是包含則返回true,否者返回false
  export default function contains(root, n) {
    let node = n;
    while (node) {
      if (node === root) {
        return true;
      }
      node = node.parentNode;
    }

    return false;
  }複製代碼
// addEventListener.js
// 這個函數主要的聚焦點是ReactDOM.unstable_batchedUpdates
// 這個api是沒有公開的一個api,可是可使用,爲了是想要將當前的組件狀態強制性的
// 更新到組件內部去而且,可是這樣作的目的可能有點粗暴。。
// 想要了解的能夠看這篇文章,或許你有新的想法
// https://zhuanlan.zhihu.com/p/20328570
  import addDOMEventListener from 'add-dom-event-listener';
  import ReactDOM from 'react-dom';

  export default function addEventListenerWrap(target, eventType, cb) {
    /* eslint camelcase: 2 */
    const callback = ReactDOM.unstable_batchedUpdates ? function run(e) {
      ReactDOM.unstable_batchedUpdates(cb, e);
    } : cb;
    return addDOMEventListener(target, eventType, callback);
  }複製代碼

getContainerRenderMixin && Portal

接下來是這兩個函數,都是來自於rc-util/lib/工具

// getContainerRenderMixin.js

  import ReactDOM from 'react-dom';

  function defaultGetContainer() {
    const container = document.createElement('div');
    document.body.appendChild(container);
    return container;
  }

  export default function getContainerRenderMixin(config) {
    const {
      autoMount = true,
      autoDestroy = true,
      isVisible,
      getComponent,
      getContainer = defaultGetContainer,
    } = config;

    let mixin;

    function renderComponent(instance, componentArg, ready) {
      if (!isVisible || instance._component || isVisible(instance)) {
        // 若是有isVisible,而且傳入的實例有_component,而且isVisible返回真則進行一下代碼
        if (!instance._container) {
          // 若是傳入實例沒有_container,則爲其添加一個默認的
          instance._container = getContainer(instance);
        }
        let component;
        if (instance.getComponent) {
          // 若是傳入實例有getComponent,則將傳入的參數傳入實例的getComponent函數
          component = instance.getComponent(componentArg);
        } else {
          // 不然就進行就是用傳入參數中的getComponent方法構造一個Component
          component = getComponent(instance, componentArg);
        }
        // unstable_renderSubtreeIntoContainer是更新組件到傳入的DOM節點上
        // 可使用它完成在組件內部實現跨組件的DOM操做
        // ReactComponent unstable_renderSubtreeIntoContainer(
        // parentComponent component,
        // ReactElement element,
        // DOMElement container,
        // [function callback]
        // )
        ReactDOM.unstable_renderSubtreeIntoContainer(instance,
          component, instance._container,
          function callback() {
            instance._component = this;
            if (ready) {
              ready.call(this);
            }
          });
      }
    }

    if (autoMount) {
      mixin = {
        ...mixin,
        // 若是是自動渲染組件,那就在DidMount和DidUpdate渲染組件
        componentDidMount() {
          renderComponent(this);
        },
        componentDidUpdate() {
          renderComponent(this);
        },
      };
    }

    if (!autoMount || !autoDestroy) {
      mixin = {
        // 若是不是自動渲染的,那就在mixin中添加一個渲染函數
        ...mixin,
        renderComponent(componentArg, ready) {
          renderComponent(this, componentArg, ready);
        },
      };
    }

    function removeContainer(instance) {
      // 用於在掛載節點remove掉添加的組件
      if (instance._container) {
        const container = instance._container;
        // 先將組件unmount
        ReactDOM.unmountComponentAtNode(container);
        // 而後在刪除掛載點
        container.parentNode.removeChild(container);
        instance._container = null;
      }
    }

    if (autoDestroy) {
      // 若是是自動銷燬的,那就在WillUnmount的時候銷燬
      mixin = {
        ...mixin,
        componentWillUnmount() {
          removeContainer(this);
        },
      };
    } else {
      mixin = {
        // 若是不是自動銷燬,那就只是在mixin中添加一個銷燬的函數
        ...mixin,
        removeContainer() {
          removeContainer(this);
        },
      };
    }
    // 最後返回構建好的mixin
    return mixin;
  }複製代碼
// Portal.js
// 這個函數就像咱們剛纔上面所提到的Potal組件的一個編寫,這樣的組件很是有用
// 咱們能夠利用這個組件建立在一些咱們所須要建立組件的地方,好比在body節點建立
// 模態框,或者在窗口節點建立fixed的定位的彈出框之類的。
// 還有就是在用完這個組件也就是在componentWillUnmount的時候必定要將節點移除
  import React from 'react';
  import PropTypes from 'prop-types';
  import { createPortal } from 'react-dom';

  export default class Portal extends React.Component {
    static propTypes = {
      getContainer: PropTypes.func.isRequired,
      children: PropTypes.node.isRequired,
    }

    componentDidMount() {
      this.createContainer();
    }

    componentWillUnmount() {
      this.removeContainer();
    }

    createContainer() {
      this._container = this.props.getContainer();
      this.forceUpdate();
    }

    removeContainer() {
      if (this._container) {
        this._container.parentNode.removeChild(this._container);
      }
    }

    render() {
      if (this._container) {
        return createPortal(this.props.children, this._container);
      }
      return null;
    }
  }複製代碼
相關文章
相關標籤/搜索