原文連接地址:github.com/Nealyang 轉載請註明出處html
這次分析setState基於0.3版本,實現比較簡單,後續會再分析目前使用的版本以及事務機制。node
流程圖大概以下react
setState的源碼比較簡單,而在執行更新的過程比較複雜。咱們直接跟着源碼一點一點屢清楚。git
/**
* Sets a subset of the state. Always use this or `replaceState` to mutate
* state. You should treat `this.state` as immutable.
*
* There is no guarantee that `this.state` will be immediately updated, so
* accessing `this.state` after calling this method may return the old value.
*
* @param {object} partialState Next partial state to be merged with state.
* @final
* @protected
*/
setState: function(partialState) {
// Merge with `_pendingState` if it exists, otherwise with existing state.
this.replaceState(merge(this._pendingState || this.state, partialState));
},
複製代碼
註釋部分說的很明確,setState後咱們不可以當即拿到咱們設置的值。github
而這段代碼也很是簡單,就是將咱們傳入的state和this._pendingState作一次merge,merge的代碼在util.js下緩存
var merge = function(one, two) {
var result = {};
mergeInto(result, one);
mergeInto(result, two);
return result;
};
function mergeInto(one, two) {
checkMergeObjectArg(one);
if (two != null) {
checkMergeObjectArg(two);
for (var key in two) {
if (!two.hasOwnProperty(key)) {
continue;
}
one[key] = two[key];
}
}
}
checkMergeObjectArgs: function(one, two) {
mergeHelpers.checkMergeObjectArg(one);
mergeHelpers.checkMergeObjectArg(two);
},
/**
* @param {*} arg
*/
checkMergeObjectArg: function(arg) {
throwIf(isTerminal(arg) || Array.isArray(arg), ERRORS.MERGE_CORE_FAILURE);
},
var isTerminal = function(o) {
return typeof o !== 'object' || o === null;
};
var throwIf = function(condition, err) {
if (condition) {
throw new Error(err);
}
};
複製代碼
診斷代碼的邏輯很是簡單,其實功能就是Object.assign()
,可是從上面代碼咱們能夠看出react源碼中的function大多都具備小而巧的特色。安全
最終,將merge後的結果傳遞給replaceState
bash
replaceState: function(completeState) {
var compositeLifeCycleState = this._compositeLifeCycleState;
invariant(
this._lifeCycleState === ReactComponent.LifeCycle.MOUNTED ||
compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
'replaceState(...): Can only update a mounted (or mounting) component.'
);
invariant(
compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
'replaceState(...): Cannot update while unmounting component or during ' +
'an existing state transition (such as within `render`).'
);
this._pendingState = completeState;
// Do not trigger a state transition if we are in the middle of mounting or
// receiving props because both of those will already be doing this.
if (compositeLifeCycleState !== CompositeLifeCycle.MOUNTING &&
compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_PROPS) {
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
var nextState = this._pendingState;
this._pendingState = null;
var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
transaction.perform(
this._receivePropsAndState,
this,
this.props,
nextState,
transaction
);
ReactComponent.ReactReconcileTransaction.release(transaction);
this._compositeLifeCycleState = null;
}
},
複製代碼
撇開50% 判斷warning代碼不說,從上面代碼咱們能夠看出,只有在componsiteLifeState不等於mounting和receiving_props 時,纔會調用 _receivePropsAndState函數來更新組件。dom
咱們能夠演示下:ide
var ExampleApplication = React.createClass({
getInitialState() {
return {}
},
componentWillMount() {
this.setState({
a: 1,
})
console.log('componentWillMount', this.state.a)
this.setState({
a: 2,
})
console.log('componentWillMount', this.state.a)
this.setState({
a: 3,
})
console.log('componentWillMount', this.state.a)
setTimeout(() => console.log('a5'), 0)
setTimeout(() => console.log(this.state.a,'componentWillMount'))
Promise.resolve('a4').then(console.log)
},
componentDidMount() {
this.setState({
a: 4,
})
console.log('componentDidMount', this.state.a)
this.setState({
a: 5,
})
console.log('componentDidMount', this.state.a)
this.setState({
a: 6,
})
console.log('componentDidMount', this.state.a)
},
render: function () {
var elapsed = Math.round(this.props.elapsed / 100);
var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0');
var message =
'React has been successfully running for ' + seconds + ' seconds.';
return React.DOM.p(null, message);
}
});
複製代碼
因此以上結果咱們能夠看出,在componentWillMount生命週期內setState後this.state不會改變,在componentDidMount是正常的。由於在上一篇文章中咱們也有說到,在mountComponent過程當中,會把compositeLifeCycleState設置爲MOUNTING狀態,在這個過程當中,是不會執行receivePropsAndState的,因此this.state也就不會更新,同理,在receivePropsAndState的過程當中,會把compositeLifeCycleState置成RECEIVING_PROPS狀態,也不會執行state更新以及render執行,在updateComponent過程當中又執行了mountComponent函數,mountComponent函數調用了render函數。
而在如今咱們使用16或者15版本中,咱們發現:
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
複製代碼
最後打印的結果爲:0,0,2,3
爲何有這樣呢?其實源於源碼中的這段代碼:
function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's // _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update // function, like setProps, setState, forceUpdate, etc.; creation and // destruction of top-level components is guarded in ReactMount.) if (!batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component); return; } dirtyComponents.push(component); } 複製代碼
由於這裏涉及到事務的概念、批量更新以及benchUpdate等,在咱們目前分析的版本中還未迭代上去,後面咱們會跟着版本升級慢慢說道。
首先咱們知道,屬性的更新必然是因爲state的更新,因此其實組件屬性的更新流程就是setState執行更新的延續,換句話說,也就是setState才能出發組件屬性的更新,源碼裏就是我在處理state更新的時候,順帶檢測了屬性的更新。因此這段源碼的開始,仍是從setState中看
_receivePropsAndState: function(nextProps, nextState, transaction) {
if (!this.shouldComponentUpdate ||
this.shouldComponentUpdate(nextProps, nextState)) {
this._performComponentUpdate(nextProps, nextState, transaction);
} else {
this.props = nextProps;
this.state = nextState;
}
},
複製代碼
代碼很是的簡單,一句話解釋:當shouldComponentUpdate爲true時,則執行更新操做。
_performComponentUpdate: function(nextProps, nextState, transaction) {
var prevProps = this.props;
var prevState = this.state;
if (this.componentWillUpdate) {
this.componentWillUpdate(nextProps, nextState, transaction);
}
this.props = nextProps;
this.state = nextState;
this.updateComponent(transaction);
if (this.componentDidUpdate) {
transaction.getReactOnDOMReady().enqueue(
this,
this.componentDidUpdate.bind(this, prevProps, prevState)
);
}
},
複製代碼
這段代碼的核心就是調用this.updateComponent
,而後對老的屬性和狀態存一下,新的更新一下而已。若是存在componentWillUpdate就執行一下,而後走更新流程。最後是把執行componentDidUpdate推入getReactOnDOMReady的隊列中,等待組件的更新。
_renderValidatedComponent: function() {
ReactCurrentOwner.current = this;
var renderedComponent = this.render();
ReactCurrentOwner.current = null;
return renderedComponent;
},
...
...
updateComponent: function(transaction) {
var currentComponent = this._renderedComponent;
var nextComponent = this._renderValidatedComponent();
if (currentComponent.constructor === nextComponent.constructor) {
if (!nextComponent.props.isStatic) {
currentComponent.receiveProps(nextComponent.props, transaction);
}
} else {
var thisID = this._rootNodeID;
var currentComponentID = currentComponent._rootNodeID;
currentComponent.unmountComponent();
var nextMarkup = nextComponent.mountComponent(thisID, transaction);
ReactComponent.DOMIDOperations.dangerouslyReplaceNodeWithMarkupByID(
currentComponentID,
nextMarkup
);
this._renderedComponent = nextComponent;
}
},
複製代碼
這裏咱們直接看updateComponent
更新流程,首先獲取當前render函數的組件,而後獲取下一次render函數的組件,_renderValidatedComponent
就是獲取下一次的render組件。 經過Constructor來判斷組件是否相同,若是相同且組件爲非靜態,則更新組件的屬性,不然卸載當前組件,而後從新mount下一個render組件而且直接暴力更新。
接着會調用render組件的receiveProps方法,其實一開始這個地方我也是很是困惑的,this指向傻傻分不清楚,後來通過各類查閱資料知道,它實際上是一個多態方法,若是是複合組件,則執行ReactCompositeComponent.receiveProps,若是是原生組件,則執行ReactNativeComponent.receiveProps。源碼分別以下:
receiveProps: function(nextProps, transaction) {
if (this.constructor.propDeclarations) {
this._assertValidProps(nextProps);
}
ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
if (this.componentWillReceiveProps) {
this.componentWillReceiveProps(nextProps, transaction);
}
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
var nextState = this._pendingState || this.state;
this._pendingState = null;
this._receivePropsAndState(nextProps, nextState, transaction);
this._compositeLifeCycleState = null;
},
複製代碼
有人可能注意到這裏的this._receivePropsAndState函數,這不是剛纔調用過麼?怎麼又調用一遍?沒錯,調用這個的this已是currentComponent了,並非上一個this。currentComponent是當前組件的render組件,也就是當前組件的子組件。子組件一樣也多是複合組件或者原生組件。正式經過這種多態的方式,遞歸的解析每級嵌套組件。最終完成從當前組件到下面的全部葉子節點的樹更新。
其實話說回來,compositeComponent最終仍是會遍歷遞歸到解析原生組件,經過咱們總體瀏覽下ReactNativeComponent.js代碼能夠看出。
咱們先從 receiveProps方法開始看
receiveProps: function(nextProps, transaction) {
assertValidProps(nextProps);
ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
this._updateDOMProperties(nextProps);
this._updateDOMChildren(nextProps, transaction);
this.props = nextProps;
},
function assertValidProps(props) {
if (!props) {
return;
}
var hasChildren = props.children != null ? 1 : 0;
var hasContent = props.content != null ? 1 : 0;
var hasInnerHTML = props.dangerouslySetInnerHTML != null ? 1 : 0;
}
複製代碼
刪除安全警告和註釋其實代碼很是簡答,首先assertValidProps就是校驗props是否合法的,更新屬性的方法就是_updateDOMProperties
_updateDOMProperties: function(nextProps) {
var lastProps = this.props;
for (var propKey in nextProps) {
var nextProp = nextProps[propKey];
var lastProp = lastProps[propKey];
//判斷新老屬性中的值是否相等
if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp) {
continue;
}
//若是是style樣式,遍歷新style,若是去舊style不相同,則把變化的存入styleUpdates對象中。最後調用 updateStylesByID 統一修改dom的style屬性。
if (propKey === STYLE) {
if (nextProp) {
nextProp = nextProps.style = merge(nextProp);
}
var styleUpdates;
for (var styleName in nextProp) {
if (!nextProp.hasOwnProperty(styleName)) {
continue;
}
if (!lastProp || lastProp[styleName] !== nextProp[styleName]) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = nextProp[styleName];
}
}
if (styleUpdates) {
ReactComponent.DOMIDOperations.updateStylesByID(
this._rootNodeID,
styleUpdates
);
}
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
var lastHtml = lastProp && lastProp.__html;
var nextHtml = nextProp && nextProp.__html;
if (lastHtml !== nextHtml) {
ReactComponent.DOMIDOperations.updateInnerHTMLByID(//注意這裏是innerHtml,因此dangerouslyInnerHTML會展現正常的HTML
this._rootNodeID,
nextProp
);
}
} else if (propKey === CONTENT) {
ReactComponent.DOMIDOperations.updateTextContentByID(//這裏是innerText,因此content與children原封不動的把HTML代碼打印到頁面上
this._rootNodeID,
'' + nextProp
);
} else if (registrationNames[propKey]) {
putListener(this._rootNodeID, propKey, nextProp);
} else {
ReactComponent.DOMIDOperations.updatePropertyByID(
this._rootNodeID,
propKey,
nextProp
);
}
}
},
複製代碼
這裏面方法沒有太多的hack技巧,很是的簡單直白,不單獨擰出來講,我直接寫到註釋裏面了。
最後直接更新組件的屬性
setValueForProperty: function(node, name, value) {
if (DOMProperty.isStandardName[name]) {
var mutationMethod = DOMProperty.getMutationMethod[name];
if (mutationMethod) {
mutationMethod(node, value);
} else if (DOMProperty.mustUseAttribute[name]) {
if (DOMProperty.hasBooleanValue[name] && !value) {
node.removeAttribute(DOMProperty.getAttributeName[name]);
} else {
node.setAttribute(DOMProperty.getAttributeName[name], value);
}
} else {
var propName = DOMProperty.getPropertyName[name];
if (!DOMProperty.hasSideEffects[name] || node[propName] !== value) {
node[propName] = value;
}
}
} else if (DOMProperty.isCustomAttribute(name)) {
node.setAttribute(name, value);
}
}
複製代碼
總體屬性更新的流程圖大概以下:
通篇讀完,是否是有種
react源碼中包含不少的點的知識,好比咱們以前說的VDOM、包括後面要去學習dom-diff、事務、緩存等等,都是一個點,而但從一個點來切入不免有的會有些枯燥沒卵用,別急別急~