啊哈,最近週末偷了下懶,去看LPL半決賽了,雖然中國隊伍沒有進入決賽,可是仍是打心底支持中國隊,但願他們可以在將來取得更好的成績。html
這個組件的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';複製代碼
在官網這裏有這麼一個解釋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
這個函數也是可以在官網這裏上找到的,是用來建立一個raect類而不是用es6語法的方法,在裏面可使用getDefaultProps()
方法app
建立當前組件的默認props,可使用getInitialState()
建立當前組件的初始state,而且在裏面寫的方法都會自動的綁定上this
,dom
也就是他所說的Autobinding
,還有一個最有用的屬性Mixins
,這個是可以在編寫不少的可以使用的外部方法傳入組件的屬性。ide
這兩個函數都是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);
}複製代碼
接下來是這兩個函數,都是來自於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;
}
}複製代碼