React 是一個十分龐大的庫,因爲要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,致使其代碼抽象化程度很高,嵌套層級很是深,閱讀其源碼是一個很是艱辛的過程。在學習 React 源碼的過程當中,給我幫助最大的就是這個系列文章,因而決定基於這個系列文章談一下本身的理解。本文會大量用到原文中的例子,想體會原汁原味的感受,推薦閱讀原文。javascript
本系列文章基於 React 15.4.2 ,如下是本系列其它文章的傳送門:
React 源碼深度解讀(一):首次 DOM 元素渲染 - Part 1
React 源碼深度解讀(二):首次 DOM 元素渲染 - Part 2
React 源碼深度解讀(三):首次 DOM 元素渲染 - Part 3
React 源碼深度解讀(四):首次自定義組件渲染 - Part 1
React 源碼深度解讀(五):首次自定義組件渲染 - Part 2
React 源碼深度解讀(六):依賴注入
React 源碼深度解讀(七):事務 - Part 1
React 源碼深度解讀(八):事務 - Part 2
React 源碼深度解讀(九):單個元素更新
React 源碼深度解讀(十):Diff 算法詳解java
在前面的系列文章裏,已經對 React 的首次渲染和 事務(transaction)做了比較詳細的介紹,接下來終於講到它最核心的一個方法:setState
。做爲聲明式的框架,React 接管了全部頁面更新相關的操做。咱們只須要定義好狀態和UI的映射關係,而後根據狀況改變狀態,它天然就能根據最新的狀態將頁面渲染出來,開發者不須要接觸底層的 DOM 操做。狀態的變動靠的就是setState
這一方法,下面咱們來揭開它神祕的面紗。node
介紹開始前,先更新一下例子:算法
class App extends Component { constructor(props) { super(props); this.state = { desc: 'start', color: 'blue' }; this.timer = setTimeout( () => this.tick(), 5000 ); } tick() { this.setState({ desc: 'end', color: 'green' }); } render() { const {desc, color} = this.state; return ( <div className="App"> <div className="App-header"> <img src="main.jpg" className="App-logo" alt="logo" /> <h1> "Welcom to React" </h1> </div> <p className="App-intro" style={{color: color}}> { desc } </p> </div> ); } } export default App;
state 保存了一個文本信息和顏色,5秒後觸發更新,改變對應的文本與樣式。segmentfault
下面咱們來看下setState
的源碼:api
function ReactComponent(props, context, updater) { this.props = props; this.context = context; this.refs = emptyObject; // We initialize the default updater but the real one gets injected by the // renderer. this.updater = updater || ReactNoopUpdateQueue; } ReactComponent.prototype.setState = function (partialState, callback) { this.updater.enqueueSetState(this, partialState); if (callback) { this.updater.enqueueCallback(this, callback, 'setState'); } };
這裏的updater
也是經過依賴注入的方式,在組件實例化的時候注入進來的。相關代碼以下:數組
// ReactCompositeComponent.js mountComponent: function ( transaction, hostParent, hostContainerInfo, context ) { ... // 這裏的 transaction 是 ReactReconcileTransaction var updateQueue = transaction.getUpdateQueue(); var doConstruct = shouldConstruct(Component); // 在這個地方將 updater 注入 var inst = this._constructComponent( doConstruct, publicProps, publicContext, updateQueue ); ... } // ReactReconcileTransaction.js var ReactUpdateQueue = require('ReactUpdateQueue'); getUpdateQueue: function () { return ReactUpdateQueue; } // ReactUpdateQuene.js var ReactUpdates = require('ReactUpdates'); enqueueSetState: function (publicInstance, partialState) { ... var internalInstance = getInternalInstanceReadyForUpdate( publicInstance, 'setState' ); if (!internalInstance) { return; } var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []); queue.push(partialState); enqueueUpdate(internalInstance); }, function enqueueUpdate(internalInstance) { ReactUpdates.enqueueUpdate(internalInstance); }
this.updater.enqueueSetState
最終落地的代碼是ReactUpdates.enqueueUpdate
。internalInstance
是用於內部操做的 ReactCompositeComponent 實例,這裏將它的_pendingStateQueue
初始化爲空數組並插入一個新的 state({desc:'end',color:'green'})。服務器
結合以前 transaction 的內容,調用關係以下:框架
從上面的調用關係圖能夠看出,transaction 最終會調用 ReactUpdates 的 runBatchedUpdates 方法。oop
function runBatchedUpdates(transaction) { var len = transaction.dirtyComponentsLength; ... for (var i = 0; i < len; i++) { var component = dirtyComponents[i]; ... ReactReconciler.performUpdateIfNecessary( component, transaction.reconcileTransaction, updateBatchNumber ); ... } }
接着是調用 ReactReconciler 的 performUpdateIfNecessary,而後到 ReactCompositeComponent 的一系列方法:
performUpdateIfNecessary: function (transaction) { if (this._pendingElement != null) { ReactReconciler.receiveComponent( this, this._pendingElement, transaction, this._context ); } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) { this.updateComponent( transaction, this._currentElement, this._currentElement, this._context, this._context ); } else { this._updateBatchNumber = null; } }, updateComponent: function ( transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext ) { var inst = this._instance; ... var nextState = this._processPendingState(nextProps, nextContext); ... this._performComponentUpdate( nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext ); }, _processPendingState: function (props, context) { var inst = this._instance; var queue = this._pendingStateQueue; var replace = this._pendingReplaceState; ... var nextState = Object.assign({}, replace ? queue[0] : inst.state); for (var i = replace ? 1 : 0; i < queue.length; i++) { var partial = queue[i]; Object.assign( nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial ); } return nextState; }, _performComponentUpdate: function ( nextElement, nextProps, nextState, nextContext, transaction, unmaskedContext ) { var inst = this._instance; ... this._updateRenderedComponent(transaction, unmaskedContext); ... }, /** * Call the component's `render` method and update the DOM accordingly. */ _updateRenderedComponent: function (transaction, context) { // ReactDOMComponent var prevComponentInstance = this._renderedComponent; // 上一次的Virtual DOM(ReactElement) var prevRenderedElement = prevComponentInstance._currentElement; // 調用 render 獲取最新的Virtual DOM(ReactElement) var nextRenderedElement = this._renderValidatedComponent(); ... if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) { ReactReconciler.receiveComponent( prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context) ); } ... },
這裏最重要的方法分別爲_processPendingState
和_updateRenderedComponent
。_processPendingState
是真正更新 state 的地方,能夠看到它其實就是一個Object.assign
的過程。在實際開發過程當中,若是須要基於以前的 state 值連續進行運算的話,若是直接經過對象去 setState 每每獲得的結果是錯誤的,看如下例子:
// this.state.count = 0 this.setState({count: this.state.count + 1}); this.setState({count: this.state.count + 1}); this.setState({count: this.state.count + 1});
假設 count 的初始值是 0 。連續 3 次 setState 後,指望的結果應該是 3 。但實際獲得的值是 1 。緣由很簡單,由於 3 次 setState 的時候,取到的this.state.count
都是 0 (state 在 set 完後不會同步更新)。若是想獲得指望的結果,代碼要改爲下面的樣子:
function add(nextState, props, context) { return {count: nextState.count + 1}; } this.setState(add); this.setState(add); this.setState(add);
結合源碼來看,若是 setState 的參數類型是 function,每次合併後的nextState
都會做爲參數傳入,獲得的結果天然是正確的了:
Object.assign( nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial, );
_updateRenderedComponent
會取出實例的 ReactDOMComponent,而後調用 render 方法,得出最新的 Virtual DOM 後啓動 Diff 的過程。
ReactReconciler.receiveComponent
最終會調用 ReactDOMComponent 的 receiveComponent 方法,進而再調用 updateComponent 方法:
updateComponent: function (transaction, prevElement, nextElement, context) { var lastProps = prevElement.props; var nextProps = this._currentElement.props; ... this._updateDOMProperties(lastProps, nextProps, transaction); this._updateDOMChildren( lastProps, nextProps, transaction, context ); ... },
這個方法只有 2 個操做,一個是更新屬性,另外一個是更新子孫結點。先來看看更新屬性的操做:
_updateDOMProperties: function (lastProps, nextProps, transaction) { var propKey; var styleName; var styleUpdates; // 刪除舊的屬性 for (propKey in lastProps) { // 篩選出後來沒有但以前有的屬性 if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] == null) { continue; } if (propKey === STYLE) { var lastStyle = this._previousStyleCopy; // 初始化 styleUpdates,以前全部的 style 屬性設置爲空 for (styleName in lastStyle) { // 將舊的 style 屬性設置爲空 if (lastStyle.hasOwnProperty(styleName)) { styleUpdates = styleUpdates || {}; styleUpdates[styleName] = ''; } } this._previousStyleCopy = null; } ... } else if ( DOMProperty.properties[propKey] || DOMProperty.isCustomAttribute(propKey)) { DOMPropertyOperations.deleteValueForProperty(getNode( this), propKey); } } for (propKey in nextProps) { var nextProp = nextProps[propKey]; var lastProp = propKey === STYLE ? this._previousStyleCopy : lastProps != null ? lastProps[propKey] : undefined; // 值相等則跳過 if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || nextProp == null && lastProp == null) { continue; } if (propKey === STYLE) { if (nextProp) { nextProp = this._previousStyleCopy = Object.assign({}, nextProp); } else { this._previousStyleCopy = null; } if (lastProp) { // Unset styles on `lastProp` but not on `nextProp`. for (styleName in lastProp) { if (lastProp.hasOwnProperty(styleName) && (!nextProp || !nextProp.hasOwnProperty(styleName))) { styleUpdates = styleUpdates || {}; styleUpdates[styleName] = ''; } } // Update styles that changed since `lastProp`. for (styleName in nextProp) { if (nextProp.hasOwnProperty(styleName) && lastProp[styleName] !== nextProp[styleName] ) { styleUpdates = styleUpdates || {}; styleUpdates[styleName] = nextProp[ styleName]; } } } else { // Relies on `updateStylesByID` not mutating `styleUpdates`. styleUpdates = nextProp; } } ... } else if ( DOMProperty.properties[propKey] || DOMProperty.isCustomAttribute(propKey)) { var node = getNode(this); // If we're updating to null or undefined, we should remove the property // from the DOM node instead of inadvertently setting to a string. This // brings us in line with the same behavior we have on initial render. if (nextProp != null) { DOMPropertyOperations.setValueForProperty(node, propKey, nextProp); } else { DOMPropertyOperations.deleteValueForProperty(node, propKey); } } } if (styleUpdates) { CSSPropertyOperations.setValueForStyles( getNode(this), styleUpdates, this ); } },
這裏主要有 2 個循環,第一個循環刪除舊的屬性,第二個循環設置新的屬性。屬性的刪除靠的是DOMPropertyOperations.deleteValueForProperty
或DOMPropertyOperations.deleteValueForAttribute
,屬性的設置靠的是DOMPropertyOperations.setValueForProperty
或DOMPropertyOperations.setValueForAttribute
。以 setValueForAttribute 爲例子,最終是調用 DOM 的 api :
setValueForAttribute: function (node, name, value) { if (!isAttributeNameSafe(name)) { return; } if (value == null) { node.removeAttribute(name); } else { node.setAttribute(name, '' + value); } },
針對 style 屬性,由styleUpdates
這個對象來收集變化的信息。它會先將舊的 style 內的全部屬性設置爲空,而後再用新的 style 來填充。得出新的 style 後調用CSSPropertyOperations.setValueForStyles
來更新:
setValueForStyles: function (node, styles, component) { var style = node.style; for (var styleName in styles) { ... if (styleValue) { style[styleName] = styleValue; } else { ... style[styleName] = ''; } } },
接下來看 updateDOMChildren 。
updateDOMChildren: function (lastProps, nextProps, transaction, context) { var lastContent = CONTENT_TYPES[typeof lastProps.children] ? lastProps.children : null; var nextContent = CONTENT_TYPES[typeof nextProps.children] ? nextProps.children : null; ... if (nextContent != null) { if (lastContent !== nextContent) { this.updateTextContent('' + nextContent); } } ... },
結合咱們的例子,最終會調用updateTextContent
。這個方法來自 ReactMultiChild ,能夠簡單理解爲 ReactDOMComponent 繼承了 ReactMultiChild 。
updateTxtContent: function (nextContent) { var prevChildren = this._renderedChildren; // Remove any rendered children. ReactChildReconciler.unmountChildren(prevChildren, false); for (var name in prevChildren) { if (prevChildren.hasOwnProperty(name)) { invariant(false, 'updateTextContent called on non-empty component.' ); } } // Set new text content. var updates = [makeTextContent(nextContent)]; processQueue(this, updates); }, function makeTextContent(textContent) { // NOTE: Null values reduce hidden classes. return { type: 'TEXT_CONTENT', content: textContent, fromIndex: null, fromNode: null, toIndex: null, afterNode: null, }; }, function processQueue(inst, updateQueue) { ReactComponentEnvironment.processChildrenUpdates( inst, updateQueue, ); }
這裏的 ReactComponentEnvironment 經過依賴注入的方式注入後,其實是 ReactComponentBrowserEnvironment 。最終會調用 DOMChildrenOperations 的 processUpdates:
processUpdates: function (parentNode, updates) { for (var k = 0; k < updates.length; k++) { var update = updates[k]; switch (update.type) { ... case 'TEXT_CONTENT': setTextContent( parentNode, update.content ); if (__DEV__) { ReactInstrumentation.debugTool.onHostOperation({ instanceID: parentNodeDebugID, type: 'replace text', payload: update.content.toString(), }); } break; ... } } }, // setTextContent.js var setTextContent = function(node, text) { if (text) { var firstChild = node.firstChild; if (firstChild && firstChild === node.lastChild && firstChild.nodeType === 3) { firstChild.nodeValue = text; return; } } node.textContent = text; };
最終的調用關係見下圖:
本文將 setState 的整個流程從頭至尾走了一遍,下一篇將會詳細的介紹 Diff 的策略。