用了這麼久的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流程仍是很複雜的,設計也很精巧,避免了重複無謂的刷新組件。它的主要流程以下 :