antd源码解读(8.1)- 番外篇 Trigger 之 index.js 头部

1,686 阅读4分钟

题外话

啊哈,最近周末偷了下懒,去看LPL半决赛了,虽然中国队伍没有进入决赛,但是还是打心底支持中国队,希望他们能够在未来取得更好的成绩。

Trigger

这个组件的index文件就有很多代码,590行代码,而且在头部引入的额外文件特别的多,所以我们这一个组件就先从这些额外的组件中开始吧,先看看这些外部方法能够做些什么。

// 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

在官网这里有这么一个解释

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.

这个函数是用来创建一个portal,而这个Portal是提供一个方法来在指定的dom元素渲染一些组件的方法。

createReactClass

这个函数也是能够在官网这里上找到的,是用来创建一个raect类而不是用es6语法的方法,在里面可以使用getDefaultProps()方法

创建当前组件的默认props,可以使用getInitialState()创建当前组件的初始state,并且在里面写的方法都会自动的绑定上this,

也就是他所说的Autobinding,还有一个最有用的属性Mixins,这个是能够在编写很多的能够使用的外部方法传入组件的属性。

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;
    }
  }