用了这么久的React,是时候啃一下源码了,原本想直接上React16的源码,发现加入React Fiber后,看起来很是痛苦.没办法只能先啃老的,再不学就跟不上了!javascript
收拾下心情,进入正题以前,先讲一下React的难点事务,React中存在大量事务的使用html
先看一下事务的代码实现:java
var Transaction = {
reinitializeTransaction: function() {
this.transactionWrappers = this.getTransactionWrappers();
if (this.wrapperInitData) {
this.wrapperInitData.length = 0;
} else {
this.wrapperInitData = [];
}
this._isInTransaction = false;
},
_isInTransaction: false,
getTransactionWrappers: null,
perform: function(method, scope, a, b, c, d, e, f) {
var ret;
try {
this._isInTransaction = true;
this.initializeAll(0);
ret = method.call(scope, a, b, c, d, e, f);
} finally {
this.closeAll(0);
this._isInTransaction = false;
}
return ret;
},
initializeAll: function() {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
}
},
closeAll: function(startIndex) {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
var initData = this.wrapperInitData[i];
wrapper.close.call(this, initData);
}
this.wrapperInitData.length = 0;
}
};复制代码
举个例子:node
function update(){
console.log('更新了')
};
Transaction.perform(update);
//会在执行update方法前,先执行initializeAll,循环调用initialize方法
//输出'更新了'
//执行closeAll,循环调用transactionWrappers中的close方法复制代码
这个事务只是抽象了功能getTransactionWrappers为null,具体的执行内容要求子类实现:react
也就是说在方法执行先后分别作一些事情!api
下面咱们看下ReactReconcileTransaction(协调事务实现)数组
var SELECTION_RESTORATION = {
initialize: ReactInputSelection.getSelectionInformation,
close: ReactInputSelection.restoreSelection
};
var EVENT_SUPPRESSION = {
initialize: function() {
var currentlyEnabled = ReactBrowserEventEmitter.isEnabled();
ReactBrowserEventEmitter.setEnabled(false);
return currentlyEnabled;
},
close: function(previouslyEnabled) {
ReactBrowserEventEmitter.setEnabled(previouslyEnabled);
}
};
var ON_DOM_READY_QUEUEING = {
initialize: function() {
this.reactMountReady.reset();
},
close: function() {
this.reactMountReady.notifyAll();
}
};
var TRANSACTION_WRAPPERS = [SELECTION_RESTORATION, EVENT_SUPPRESSION, ON_DOM_READY_QUEUEING];
function ReactReconcileTransaction(useCreateElement) {
this.reinitializeTransaction();
}
var Mixin = {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
getUpdateQueue: function() {
return ReactUpdateQueue;
}
};
_assign(ReactReconcileTransaction.prototype, Transaction, Mixin);复制代码
ReactReconcileTransaction继承了Transaction,重写了getTransactionWrappers方法
getTransactionWrappers: function () { return TRANSACTION_WRAPPERS; },复制代码
官方源码的解析图:浏览器
简单地说,一个Transaction 就是将须要执行的 method 使用 wrapper 封装起来,再经过 Transaction 提供的 perform 方法执行。而在 perform 以前,先执行全部 wrapper 中的 initialize 方法;perform 完成以后(即 method 执行后)再执行全部的 close 方法。一组 initialize 及 close 方法称为一个 wrapper,从上面的示例图中能够看出 Transaction 支持多个 wrapper 叠加。
缓存
下面开始进入正题:bash
import React from 'react';
import ReactDOM from 'react-dom';
class HelloWorld extends React.Component {
constructor(props) {
super(props);
this.state = {
message: "hello, world",
className: 'react-wrap'
}
}
componentWillMount() {
debugger console.log("component will mount");
}
componentWillUpdate() {
debugger console.log("component will update");
}
componentDidUpdate() {
debugger console.log("component did update");
}
componentDidMount() {
debugger console.log("componentDidMount");
}
handleMessage() {
debugger this.setState({
message: '哈哈',
className: 'list-wrap'
});
}
render() {
return < span className = {
this.state.className
}
onClick = {
this.handleMessage.bind(this)
} > {
this.state.message
} < /span> }}ReactDOM.render( <HelloWorld/ > ,
document.getElementById('screen-check'))
即: ReactDOM.render(React.createElement(HelloWorld, null), document.getElementById('screen-check'));
复制代码
<HelloWorld/>转换后就是 React.createElement(HelloWorld, null);
ReactElement.createElement = function(type, config, children) {
var propName;
var props = {};
var key = null;
var ref = null;
var self = null;
var source = null;
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null: config.__self;
source = config.__source === undefined ? null: config.__source;
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
}
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
};复制代码
createElement对参数进行整理后调用ReactElement
返回:
获得Element,接着调用render
其中:nextElement:<HelloWorld/> container:document.getElementById('screen-check')
接下来调用_renderSubtreeIntoContainer,
将传入的nextElement作了统一包装nextWrappedElement,这样能够保持根组件统一,方便处理
_renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) {
var nextWrappedElement = React.createElement(TopLevelWrapper, {
child: nextElement
});
var nextContext;
if (parentComponent) {
var parentInst = ReactInstanceMap.get(parentComponent);
nextContext = parentInst._processChildContext(parentInst._context);
} else {
nextContext = emptyObject;
}
var prevComponent = getTopLevelWrapperInContainer(container);
if (prevComponent) {
var prevWrappedElement = prevComponent._currentElement;
var prevElement = prevWrappedElement.props.child;
if (shouldUpdateReactComponent(prevElement, nextElement)) {
var publicInst = prevComponent._renderedComponent.getPublicInstance();
var updatedCallback = callback &&
function() {
callback.call(publicInst);
};
ReactMount._updateRootComponent(prevComponent, nextWrappedElement, nextContext, container, updatedCallback);
return publicInst;
} else {
ReactMount.unmountComponentAtNode(container);
}
}
var reactRootElement = getReactRootElementInContainer(container);
var containerHasReactMarkup = reactRootElement && !!internalGetID(reactRootElement);
var containerHasNonRootReactChild = hasNonRootReactChild(container);
var shouldReuseMarkup = containerHasReactMarkup && !prevComponent && !containerHasNonRootReactChild;
var component = ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, nextContext)._renderedComponent.getPublicInstance();
if (callback) {
callback.call(component);
}
return component;
},
复制代码
首次挂载,prevComponent不存在,直接调用_renderNewRootComponent
ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, nextContext)复制代码
_renderNewRootComponent: function(nextElement, container, shouldReuseMarkup, context) {
var componentInstance = instantiateReactComponent(nextElement, false);
ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);
var wrapperID = componentInstance._instance.rootID;
instancesByReactRootID[wrapperID] = componentInstance;
return componentInstance;
},
复制代码
// 初始化组件对象,node是一个ReactElement对象,是节点元素在React中的表示
function instantiateReactComponent(node) {
var instance;
var isEmpty = node === null || node === false;
if (isEmpty) {
// 空对象
instance = ReactEmptyComponent.create(instantiateReactComponent);
} else if (typeof node === 'object') {
// 组件对象,包括DOM原生的和React自定义组件
var element = node;
// 根据ReactElement中的type字段区分
if (typeof element.type === 'string') {
// type为string则表示DOM原生对象,好比div span等。能够参看上面babel转译的那段代码
instance = ReactNativeComponent.createInternalComponent(element);
} else if (isInternalComponentType(element.type)) {
// 保留给之后版本使用,此处暂时不会涉及到
instance = new element.type(element);
} else {
// React自定义组件
instance = new ReactCompositeComponentWrapper(element);
}
} else if (typeof node === 'string' || typeof node === 'number') {
// 元素是一个string时,对应的好比<span>123</span> 中的123
// 本质上它不是一个ReactElement,但为了统一,也按照一样流程处理,称为ReactDOMTextComponent
instance = ReactNativeComponent.createInstanceForText(node);
} else {
// 无处理
}
// 初始化参数,这两个参数是DOM diff时用到的
instance._mountIndex = 0;
instance._mountImage = null;
return instance;
}复制代码
node |
实际参数 | 结果 |
---|---|---|
null /false |
空 | 建立ReactEmptyComponent 组件 |
object && type === string |
虚拟DOM | 建立ReactDOMComponent 组件 |
object && type !== string |
React组件 | 建立ReactCompositeComponent 组件 |
string |
字符串 | 建立ReactTextComponent 组件 |
number |
数字 | 建立ReactTextComponent 组件 |
var ReactCompositeComponent = {
construct: function(element) {
this._currentElement = element;
this._rootNodeID = 0;
this._compositeType = null;
this._instance = null;
this._hostParent = null;
this._hostContainerInfo = null;
this._updateBatchNumber = null;
this._pendingElement = null;
this._pendingStateQueue = null;
this._pendingReplaceState = false;
this._pendingForceUpdate = false;
this._renderedNodeType = null;
this._renderedComponent = null;
this._context = null;
this._mountOrder = 0;
this._topLevelWrapper = null;
this._pendingCallbacks = null;
this._calledComponentWillUnmount = false;
},
mountComponent: function(transaction, hostParent, hostContainerInfo, context) {}
...
}复制代码
ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);复制代码
function batchedUpdates(callback, a, b, c, d, e) {
return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}复制代码
查看batchingStrategy代码:
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}
_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
}
});
var transaction = new ReactDefaultBatchingStrategyTransaction();
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
}
};
module.exports = ReactDefaultBatchingStrategy;复制代码
ReactDefaultBatchingStrategy.batchedUpdates内容使用了开始提到的transaction
在执行batchedMountComponentIntoNode 前 先执行RESET_BATCHED_UPDATES,FLUSH_BATCHED_UPDATES的initialize方法,这里是个空函数
接着进入事务调用
transaction.perform(callback, null, a, b, c, d, e);复制代码
这是一个事务嵌套:
执行过程以下图:
根据上面的事务详细图可知道
ReactReconcileTransaction事务中
到此先总结一下:
接下来通过两个事务嵌套,执行
function mountComponentIntoNode(wrapperInstance, container, transaction, shouldReuseMarkup, context) {
// 调用对应中的ReactCompositeComponent 中mountComponent方法来渲染组件。
// mountComponent返回React组件解析的HTML。不一样的ReactComponent的mountComponent策略不一样
var markup = ReactReconciler.mountComponent(wrapperInstance, transaction, null, ReactDOMContainerInfo(wrapperInstance, container), context, 0
/* parentDebugID */
);
wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
// 将解析出来的HTML插入DOM中
ReactMount._mountImageIntoNode(markup, container, wrapperInstance, shouldReuseMarkup, transaction);
}复制代码
进入mountComponentIntoNode先看markup 调用ReactReconciler.mountComponent
mountComponent: function (internalInstance, transaction, hostParent, hostContainerInfo, context, parentDebugID){
var markup = internalInstance.mountComponent(transaction, hostParent, hostContainerInfo, context, parentDebugID);
return markup;
}复制代码
ReactReconciler.mountComponent中使用的是对应组件的实例调用本身的mountComponent方法
这里的实例internalInstance对应的是ReactCompositeComponent类型实例
var ReactCompositeComponent = {
construct: function(element) {
//...省略
},
mountComponent: function(transaction, hostParent, hostContainerInfo, context) {
var _this = this;
this._context = context;
this._mountOrder = nextMountID++;
this._hostParent = hostParent;
this._hostContainerInfo = hostContainerInfo;
var publicProps = this._currentElement.props;
var publicContext = this._processContext(context);
var Component = this._currentElement.type;
var updateQueue = transaction.getUpdateQueue();
// Initialize the public class
var doConstruct = shouldConstruct(Component);
var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
var renderedElement;
if (!doConstruct && (inst == null || inst.render == null)) {
renderedElement = inst;
warnIfInvalidElement(Component, renderedElement); ! (inst === null || inst === false || React.isValidElement(inst)) ? process.env.NODE_ENV !== 'production' ? invariant(false, '%s(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.', Component.displayName || Component.name || 'Component') : _prodInvariant('105', Component.displayName || Component.name || 'Component') : void 0;
inst = new StatelessComponent(Component);
this._compositeType = CompositeTypes.StatelessFunctional;
} else {
if (isPureComponent(Component)) {
this._compositeType = CompositeTypes.PureClass;
} else {
this._compositeType = CompositeTypes.ImpureClass;
}
}
inst.props = publicProps;
inst.context = publicContext;
inst.refs = emptyObject;
inst.updater = updateQueue;
this._instance = inst;
ReactInstanceMap.set(inst, this);
var initialState = inst.state;
if (initialState === undefined) {
inst.state = initialState = null;
} ! (typeof initialState === 'object' && !Array.isArray(initialState)) ? process.env.NODE_ENV !== 'production' ? invariant(false, '%s.state: must be set to an object or null', this.getName() || 'ReactCompositeComponent') : _prodInvariant('106', this.getName() || 'ReactCompositeComponent') : void 0;
this._pendingStateQueue = null;
this._pendingReplaceState = false;
this._pendingForceUpdate = false;
var markup;
markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
if (inst.componentDidMount) {
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
}
return markup;
}
}复制代码
这里先看一下this._processContext(context);
_processContext: function(context) {
var maskedContext = this._maskContext(context);
return maskedContext;
},复制代码
在使用ContextAPI时,要在自定义组件中定义:
从代码中能够看到若是组件不定义contextTypes,返回的Context就是{},取不到值
接着是建立实例
var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
_constructComponent: function (doConstruct, publicProps, publicContext, updateQueue) {
return this._constructComponentWithoutOwner(doConstruct, publicProps, publicContext, updateQueue);
},复制代码
_constructComponentWithoutOwner: function (doConstruct, publicProps, publicContext, updateQueue) {
var Component = this._currentElement.type;
return new Component(publicProps, publicContext, updateQueue);
}复制代码
建立实例的是以前包装的根组件
接下来是判断自定义组件的方式:
function isPureComponent(Component) {
return !!(Component.prototype && Component.prototype.isPureReactComponent);
}复制代码
都知道自定义组件有两种形式:
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
或者
class Greeting extends React.PureComponent {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}复制代码
React.PureComponent
与 React.Component
很类似。二者的区别在于 React.Component
并未实现 shouldComponentUpdate()
,而 React.PureComponent
中以浅层对比 prop 和 state 的方式来实现了该函数。
React.PureComponent
中的 shouldComponentUpdate()
仅做对象的浅层比较
获得this._compositeType类型后用于后面判断shouldComponentUpdate()要不要作浅层比较
inst.props = publicProps;
inst.context = publicContext;
inst.refs = emptyObject;
inst.updater = updateQueue;//用于setSate和foreUpdate方法调用时使用复制代码
ReactCompositeComponent实例经过_instance属性引入TopLevelWrapper根组件实例
TopLevelWrapper组件实例经过_reactInternalInstance属性引用ReactCompositeComponent实例
接着初始化挂载
performInitialMount: function(renderedElement, hostParent, hostContainerInfo, transaction, context) {
var inst = this._instance;
var debugID = 0;
if (inst.componentWillMount) {
inst.componentWillMount();
// When mounting, calls to `setState` by `componentWillMount` will set
// `this._pendingStateQueue` without triggering a re-render.
if (this._pendingStateQueue) {
inst.state = this._processPendingState(inst.props, inst.context);
}
}
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent();
}
var nodeType = ReactNodeTypes.getType(renderedElement);
this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY
/* shouldHaveDebugID */
);
this._renderedComponent = child;
var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID);
return markup;
}复制代码
这里出现了第一个生命周期componentWillMount
包装根组件没有定义componentWillMount跳过.
而后调用render方法
_renderValidatedComponentWithoutOwnerOrContext: function() {
var inst = this._instance;
var renderedElement;
renderedElement = inst.render();
return renderedElement;
}复制代码
renderedElement=this._renderValidatedComponent();
返回的就是咱们定义的<HelloWorld/>对象,到此进入本身的世界
接着就是递归调用
instantiateReactComponent()实例化组件根据ReactElement中不一样的type字段,建立不一样类型的组件对象
根据renderedElement的type Class HelloWorld建立ReactCompositeComponent实例
而后仍是调用ReactCompositeComponent实例的mountComponent方法
此次建立自定义组件HelloWorld
执行HelloWorld对应的ReactCompositeComponent实例初始化挂载时,定义了生命周期
开始调用第一个生命周期
接着调用render
// If not a stateless component, we now render
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent();
}复制代码
renderedElement = this._renderValidatedComponent();
renderedElement的结构为
接着又是递归调用:
此次递归不一样的是renderedElement的type类型是String
根据instantiateReactComponent()实例化组件,此次是ReactDOMComponent组件实例
ReactDOMComponent组件的mountComponent也跟ReactCompositeComponent实例不一样
这里包含了建立dom,添加事件,兼容不一样浏览器,是几种组件最复杂的
ReactDOMComponent = {
mountComponent: function(transaction, hostParent, hostContainerInfo, context) {
this._rootNodeID = globalIdCounter++;
this._domID = hostContainerInfo._idCounter++;
this._hostParent = hostParent;
this._hostContainerInfo = hostContainerInfo;
var props = this._currentElement.props;
switch (this._tag) {
case 'audio':
case 'form':
case 'iframe':
case 'img':
case 'link':
case 'object':
case 'source':
case 'video':
this._wrapperState = {
listeners: null
};
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
case 'input':
ReactDOMInput.mountWrapper(this, props, hostParent);
props = ReactDOMInput.getHostProps(this, props);
transaction.getReactMountReady().enqueue(trackInputValue, this);
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
case 'option':
ReactDOMOption.mountWrapper(this, props, hostParent);
props = ReactDOMOption.getHostProps(this, props);
break;
case 'select':
ReactDOMSelect.mountWrapper(this, props, hostParent);
props = ReactDOMSelect.getHostProps(this, props);
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
case 'textarea':
ReactDOMTextarea.mountWrapper(this, props, hostParent);
props = ReactDOMTextarea.getHostProps(this, props);
transaction.getReactMountReady().enqueue(trackInputValue, this);
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
}
var namespaceURI;
var parentTag;
if (hostParent != null) {
namespaceURI = hostParent._namespaceURI;
parentTag = hostParent._tag;
} else if (hostContainerInfo._tag) {
namespaceURI = hostContainerInfo._namespaceURI;
parentTag = hostContainerInfo._tag;
}
if (namespaceURI == null || namespaceURI === DOMNamespaces.svg && parentTag === 'foreignobject') {
namespaceURI = DOMNamespaces.html;
}
if (namespaceURI === DOMNamespaces.html) {
if (this._tag === 'svg') {
namespaceURI = DOMNamespaces.svg;
} else if (this._tag === 'math') {
namespaceURI = DOMNamespaces.mathml;
}
}
this._namespaceURI = namespaceURI;
if (process.env.NODE_ENV !== 'production') {
var parentInfo;
if (hostParent != null) {
parentInfo = hostParent._ancestorInfo;
} else if (hostContainerInfo._tag) {
parentInfo = hostContainerInfo._ancestorInfo;
}
if (parentInfo) {
validateDOMNesting(this._tag, null, this, parentInfo);
}
this._ancestorInfo = validateDOMNesting.updatedAncestorInfo(parentInfo, this._tag, this);
}
var mountImage;
if (transaction.useCreateElement) {
var ownerDocument = hostContainerInfo._ownerDocument;
var el;
if (namespaceURI === DOMNamespaces.html) {
if (this._tag === 'script') {
// Create the script via .innerHTML so its "parser-inserted" flag is
// set to true and it does not execute
var div = ownerDocument.createElement('div');
var type = this._currentElement.type;
div.innerHTML = '<' + type + '></' + type + '>';
el = div.removeChild(div.firstChild);
} else if (props.is) {
el = ownerDocument.createElement(this._currentElement.type, props.is);
} else {
el = ownerDocument.createElement(this._currentElement.type);
}
} else {
el = ownerDocument.createElementNS(namespaceURI, this._currentElement.type);
}
ReactDOMComponentTree.precacheNode(this, el);
this._flags |= Flags.hasCachedChildNodes;
if (!this._hostParent) {
DOMPropertyOperations.setAttributeForRoot(el);
}
this._updateDOMProperties(null, props, transaction);
var lazyTree = DOMLazyTree(el);
this._createInitialChildren(transaction, props, context, lazyTree);
mountImage = lazyTree;
} else {
var tagOpen = this._createOpenTagMarkupAndPutListeners(transaction, props);
var tagContent = this._createContentMarkup(transaction, props, context);
if (!tagContent && omittedCloseTags[this._tag]) {
mountImage = tagOpen + '/>';
} else {
mountImage = tagOpen + '>' + tagContent + '</' + this._currentElement.type + '>';
}
}
switch (this._tag) {
case 'input':
transaction.getReactMountReady().enqueue(inputPostMount, this);
if (props.autoFocus) {
transaction.getReactMountReady().enqueue(AutoFocusUtils.focusDOMComponent, this);
}
break;
case 'textarea':
transaction.getReactMountReady().enqueue(textareaPostMount, this);
if (props.autoFocus) {
transaction.getReactMountReady().enqueue(AutoFocusUtils.focusDOMComponent, this);
}
break;
case 'select':
if (props.autoFocus) {
transaction.getReactMountReady().enqueue(AutoFocusUtils.focusDOMComponent, this);
}
break;
case 'button':
if (props.autoFocus) {
transaction.getReactMountReady().enqueue(AutoFocusUtils.focusDOMComponent, this);
}
break;
case 'option':
transaction.getReactMountReady().enqueue(optionPostMount, this);
break;
}
return mountImage;
}
}复制代码
这里先建立span标签:
接着在ReactDOMComponent对应的实例属性_hostNode 引用span节点
在dom节点span添加"__reactInternalInstance$r5bs9lz1m3"属性引用ReactDOMComponent对应的实例,internalInstanceKey的值为"__reactInternalInstance$r5bs9lz1m3"
接下来在dom节点上添加属性
this._updateDOMProperties(null, props, transaction);
在DOMProperty.properties中找到'className'对应的属性名称为class,在dom节点上设置classs属性
function enqueuePutListener(inst, registrationName, listener, transaction) {
var containerInfo = inst._hostContainerInfo;
var isDocumentFragment = containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE;
var doc = isDocumentFragment ? containerInfo._node: containerInfo._ownerDocument;
listenTo(registrationName, doc);
transaction.getReactMountReady().enqueue(putListener, {
inst: inst,
registrationName: registrationName,
listener: listener
});
}复制代码
在listenTo(registrationName, doc);中,调用了
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(dependency, topEventMapping[dependency], mountAt);复制代码
trapBubbledEvent方法中对document方法添加了click,事件作了代理
这里的注册事件的回调函数很是重要,
是后面点击调用处理事件,触发setState的起点,点击事件发生先触发ReactEventListener.dispatchEvent
中间穿插事务transaction,提升setState更新效率
trapBubbledEvent: function (topLevelType, handlerBaseName, element) {
return EventListener.listen(element, handlerBaseName, ReactEventListener.dispatchEvent.bind(null, topLevelType));
},复制代码
listenTo(registrationName, doc);执行完
transaction.getReactMountReady().enqueue(putListener, {
inst: inst,
registrationName: registrationName,
listener: listener
});复制代码
在ReactReconcileTransaction对应的事务实例
transaction.reactMountReady的数组中收集,等更新完毕后,触发对应的close方法时调用
接下来处理'children',这个不属于dom属性
更新完属性接着
this._updateDOMProperties(null, props, transaction);//更新属性
var lazyTree = DOMLazyTree(el);
this._createInitialChildren(transaction, props, context, lazyTree);
mountImage = lazyTree;复制代码
_createInitialChildren中处理children复制代码
开始挂载children,循环children数组
又回到
根据instantiateReactComponent()实例化组件,此次是ReactDOMTextComponent组件实例
var ReactDOMTextComponent = function(text) {
// TODO: This is really a ReactText (ReactNode), not a ReactElement
this._currentElement = text;
this._stringText = '' + text;
// ReactDOMComponentTree uses these:
this._hostNode = null;
this._hostParent = null;
// Properties
this._domID = 0;
this._mountIndex = 0;
this._closingComment = null;
this._commentNodes = null;
};复制代码
mountComponent: function(transaction, hostParent, hostContainerInfo, context) {
var domID = hostContainerInfo._idCounter++;
var openingValue = ' react-text: ' + domID + ' ';
var closingValue = ' /react-text ';
this._domID = domID;
this._hostParent = hostParent;
if (transaction.useCreateElement) {
var ownerDocument = hostContainerInfo._ownerDocument;
var openingComment = ownerDocument.createComment(openingValue);
var closingComment = ownerDocument.createComment(closingValue);
var lazyTree = DOMLazyTree(ownerDocument.createDocumentFragment());
DOMLazyTree.queueChild(lazyTree, DOMLazyTree(openingComment));
if (this._stringText) {
DOMLazyTree.queueChild(lazyTree, DOMLazyTree(ownerDocument.createTextNode(this._stringText)));
}
DOMLazyTree.queueChild(lazyTree, DOMLazyTree(closingComment));
ReactDOMComponentTree.precacheNode(this, openingComment);
this._closingComment = closingComment;
return lazyTree;
} else {
var escapedText = escapeTextContentForBrowser(this._stringText);
if (transaction.renderToStaticMarkup) {
// Normally we'd wrap this between comment nodes for the reasons stated // above, but since this is a situation where React won't take over
// (static pages), we can simply return the text as it is.
return escapedText;
}
return '<!--' + openingValue + '-->' + escapedText + '<!--' + closingValue + '-->';
}
}复制代码
在mountChildren中,循环children数组,instantiateReactComponent()实例化组件
children
mountChildren: function (nestedChildren, transaction, context) {
var children = this._reconcilerInstantiateChildren(nestedChildren, transaction, context);
this._renderedChildren = children;
var mountImages = [];
var index = 0;
for (var name in children) {
if (children.hasOwnProperty(name)) {
var child = children[name];
var selfDebugID = 0;
var mountImage = ReactReconciler.mountComponent(child, transaction, this, this._hostContainerInfo, context, selfDebugID);
child._mountIndex = index++;
mountImages.push(mountImage);
}
}
return mountImages;
}复制代码
而后又开始循环children对象调用ReactDOMTextComponent对应实例的mountComponent
ReactDOMTextComponent的mountComponent比较简单建立document.createTextNode文本节点插入DocumentFragment文档片断
处理完以后,将文档片断DocumentFragment,循环插入
上面lazyTree的dom节点span
这样递归就完成回到
ReactCompositeComponentWrapper对应的实例HelloWorld
这个时候虽然Dom节点建立完毕,可是尚未插入页面节点
咱们都知道componentDidMount是在插入页面后执行的,这里的实现就是先将componentDidMount收集到事务transaction的reactMountReady中,等到更新挂载到页面中在调用
这里如今存储了两个,一个是以前添加onClick时收集的
接下来就回到ReactCompositeComponentWrapper对应的TopLevelWrapper
根包装组件没有componentDidMount,直接返回markup,如今回到了mountComponentIntoNode,也就是两个事务要执行的方法,
接着就要将dom节点插入文档,执行事务的close了
接着插入container页面节点,完成以后mountComponentIntoNode方法调用结束
根据上面的事务详细图可知道,先ReactReconcileTransaction事务中
先执行:putListener
接着执行:componentDidMount
总结一下:
ReactDOM.render()是渲染React组件并插入到DOM中的入口方法,它的执行流程大概为
感受文章写的有点长,后面就简单讲解了:
仍是要用到事务:
ReactComponent.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};复制代码
看到setState调用的是this.updater.enqueueSetState(this, partialState)
根据上面的挂载过程能够看到this.updater是在ReactCompositeComponent实例调用
mountComponent挂载的
var ReactCompositeComponent = {
construct: function(element) {
//...省略
},
mountComponent: function(transaction, hostParent, hostContainerInfo, context) {
//...
var updateQueue = transaction.getUpdateQueue();
inst.props = publicProps;
inst.context = publicContext;
inst.refs = emptyObject;
inst.updater = updateQueue;//
//...
}
}复制代码
能够看到 inst.updater的值transaction.getUpdateQueue();
获得的就是ReactUpdateQueue.js中导出的对象
var ReactUpdateQueue = {
//....
enqueueCallback: function (publicInstance, callback, callerName) {
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
if (!internalInstance) {
return null;
}
if (internalInstance._pendingCallbacks) {
internalInstance._pendingCallbacks.push(callback);
} else {
internalInstance._pendingCallbacks = [callback];
}
enqueueUpdate(internalInstance);
},
enqueueCallbackInternal: function (internalInstance, callback) {
if (internalInstance._pendingCallbacks) {
internalInstance._pendingCallbacks.push(callback);
} else {
internalInstance._pendingCallbacks = [callback];
}
enqueueUpdate(internalInstance);
}
//....
}复制代码
从第一部分挂载中说明的重点注册onClick开始:
根据第一部分在document上注册click事件的回调函数是ReactEventListener.dispatchEvent
因此点击时触发
这里开始进入批量更新:ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
又进入了ReactDefaultBatchingStrategy事务中
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
}
};复制代码
根据上面的事务详细图可知道
从发生的原生,沿着冒泡的顺序向上找到对应的父组件的队列,循环调用处理
bookKeeping.ancestors列表结构:
通过一些复杂的前期处理后,调用定义的handleMessage
接着就是调用setSate(),先将传入的 {className: "list-wrap",message: "哈哈"}放入实例
ReactCompositeComponentWrapper的实例HelloWorld的_pendingStateQueue中
接着调用
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}复制代码
function enqueueUpdate(component) {
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}复制代码
因为这是处于事务中batchingStrategy.isBatchingUpdates=true
组件就会放在dirtyComponents,
这就是React高效的更新,处在事务中,不管调用setState多少次都只作一次更新,全部的setState都会放入dirtyComponents中
过程以下图所示:
这样处在事务中的handleTopLevelImpl就执行完毕
接下来要执行事务的close方法去更新内容了
flushBatchedUpdates = function() {
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}
}
};复制代码
执行close方法调用flushBatchedUpdates方法,其中又包含了一个事务
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction);复制代码
这个ReactUpdatesFlushTransaction事务中包含两块以下:
var NESTED_UPDATES = {
initialize: function() {
this.dirtyComponentsLength = dirtyComponents.length;
},
close: function() {
if (this.dirtyComponentsLength !== dirtyComponents.length) {
dirtyComponents.splice(0, this.dirtyComponentsLength);
flushBatchedUpdates();
} else {
dirtyComponents.length = 0;
}
}
};
var UPDATE_QUEUEING = {
initialize: function() {
this.callbackQueue.reset();
},
close: function() {
this.callbackQueue.notifyAll();
}
};
var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];复制代码
根据上面的事务详细图可知道
ReactReconcileTransaction事务中
开始执行runBatchedUpdates
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
dirtyComponents.sort(mountOrderComparator);
for (var i = 0; i < len; i++) {
// dirtyComponents中取出一个component
var component = dirtyComponents[i];
// 取出dirtyComponent中的未执行的callback,下面就准备执行它了
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
var markerName;
if (ReactFeatureFlags.logTopLevelRenders) {
var namedComponent = component;
if (component._currentElement.props === component._renderedComponent._currentElement) {
namedComponent = component._renderedComponent;
}
}
// 执行updateComponent
ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction);
// 执行dirtyComponent中以前未执行的callback
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
}
}
}
}复制代码
runBatchedUpdates循环遍历dirtyComponents数组,主要干两件事。首先执行performUpdateIfNecessary来刷新组件的view,而后执行以前阻塞的callback。下面来看performUpdateIfNecessary。
performUpdateIfNecessary: function (transaction) {
if (this._pendingElement != null) {
// receiveComponent会最终调用到updateComponent,从而刷新View
ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
}
if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
// 执行updateComponent,从而刷新View。这个流程在React生命周期中讲解过
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
}
}复制代码
开始调用ReactCompositeComponentWrapper对应的实例HelloWorld的updateComponent
这里若是有componentWillReceiveProps调用了这个生命周期
合并state
变化,统一处理
都知道自定义组件有两种形式:
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
或者
class Greeting extends React.PureComponent {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}复制代码
React.PureComponent
与 React.Component
很类似。二者的区别在于 React.Component
并未实现 shouldComponentUpdate()
,而 React.PureComponent
中以浅层对比 prop 和 state 的方式来实现了该函数。
React.PureComponent
中的 shouldComponentUpdate()
仅做对象的浅层比较
代码处理判断在这里:
接着往下走:
this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext);
//ReactCompositeComponent中
_performComponentUpdate: function(nextElement, nextProps, nextState, nextContext, transaction, unmaskedContext) {
var _this2 = this;
var inst = this._instance;
var hasComponentDidUpdate = Boolean(inst.componentDidUpdate);
var prevProps;
var prevState;
var prevContext;
if (hasComponentDidUpdate) {
prevProps = inst.props;
prevState = inst.state;
prevContext = inst.context;
}
if (inst.componentWillUpdate) {
if (process.env.NODE_ENV !== 'production') {
measureLifeCyclePerf(function() {
return inst.componentWillUpdate(nextProps, nextState, nextContext);
},
this._debugID, 'componentWillUpdate');
} else {
inst.componentWillUpdate(nextProps, nextState, nextContext);
}
}
this._currentElement = nextElement;
this._context = unmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
this._updateRenderedComponent(transaction, unmaskedContext);
if (hasComponentDidUpdate) {
if (process.env.NODE_ENV !== 'production') {
transaction.getReactMountReady().enqueue(function() {
measureLifeCyclePerf(inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), _this2._debugID, 'componentDidUpdate');
});
} else {
transaction.getReactMountReady().enqueue(inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), inst);
}
}
}复制代码
调用componentWillUpdate生命周期
接下来进入:
_updateRenderedComponent: function(transaction, context) {
var prevComponentInstance = this._renderedComponent;
var prevRenderedElement = prevComponentInstance._currentElement;
// _renderValidatedComponent内部会调用render,获得ReactElement
var nextRenderedElement = this._renderValidatedComponent();
// 判断是否作DOM diff。React为了简化递归diff,认为组件层级不变,且type和key不变(key用于listView等组件,不少时候咱们没有设置type)才update,不然先unmount再从新mount
if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
// 递归updateComponent,更新子组件的Virtual DOM
ReactReconciler.receiveComponent(prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context));
} else {
var oldNativeNode = ReactReconciler.getNativeNode(prevComponentInstance);
// 不作DOM diff,则先卸载掉,而后再加载。也就是先unMountComponent,再mountComponent
ReactReconciler.unmountComponent(prevComponentInstance, false);
this._renderedNodeType = ReactNodeTypes.getType(nextRenderedElement);
// 由ReactElement建立ReactComponent
this._renderedComponent = this._instantiateReactComponent(nextRenderedElement);
// mountComponent挂载组件,获得组件对应HTML
var nextMarkup = ReactReconciler.mountComponent(this._renderedComponent, transaction, this._nativeParent, this._nativeContainerInfo, this._processChildContext(context));
// 将HTML插入DOM中
this._replaceNodeWithMarkup(oldNativeNode, nextMarkup, prevComponentInstance);
}
}复制代码
再次调用render方法:
经过shouldUpdateReactComponent(prevElement, nextElement)比较
判断是否作DOM diff
和mountComponent中同样,updateComponent也是用递归的方式将各子组件进行update的。这里要特别注意的是DOM diff。DOM diff是React中渲染加速的关键所在,它会帮咱们算出virtual DOM中真正变化的部分,并对这部分进行原生DOM操做。为了不循环递归对比节点的低效率,React中作了假设,即只对层级不变,type不变,key不变的组件进行Virtual DOM更新
这里是须要的
总结 setState流程仍是很复杂的,设计也很精巧,避免了重复无谓的刷新组件。它的主要流程以下 :