摘要:流程圖大概如下的源碼比較簡單,而在執行更新的過程比較復雜。這段代碼的核心就是調用,然后對老的屬性和狀態存一下,新的更新一下而已。最后是把執行推入的隊列中,等待組件的更新。最后調用統一修改的屬性。
原文鏈接地址:https://github.com/Nealyang%EF%BC%9A%E7%8A%B6%E6%80%81%E3%80%81%E5%B1%9E%E6%80%A7%E6%9B%B4%E6%96%B0%20-%3E%20setState.md) 轉載請注明出處狀態更新
此次分析setState基于0.3版本,實現比較簡單,后續會再分析目前使用的版本以及事務機制。
流程圖大概如下
setState的源碼比較簡單,而在執行更新的過程比較復雜。我們直接跟著源碼一點一點屢清楚。
ReactCompositeComponent.js
/** * 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后我們不能夠立即拿到我們設置的值。
而這段代碼也非常簡單,就是將我們傳入的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
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函數來更新組件。
我們可以演示下:
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、事務、緩存等等,都是一個點,而但從一個點來切入難免有的會有些枯燥沒卵用,別急別急~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97122.html
本篇主要和大家溝通關于ahooks ,我們可以理解為加深對 React hooks 的了解。 我們先說下關于抽象自定義 hooks。構建屬于自己的 React hooks 工具庫。 其實我們應該培養閱讀學習源碼的習慣,工具庫是一個對源碼閱讀不錯的選擇。 注:本系列對 ahooks 的源碼解析是基于v3.3.13。 現在就進入主題用ahooks 來封裝 React要注意的時機? Fun...
摘要:只涉及了,其他均沒有自己實現。這種組件的復用性是最強的。所以會新建,只繼承的原型,不包括,以此來節省內存。 showImg(https://segmentfault.com/img/remote/1460000019783989); 一、React.Component() GitHub:https://github.com/AttackXiaoJinJin/reactExplain/...
摘要:今天我們就來解讀一下的源碼。比較有意思,將定時器以方式提供出來,并且提供了方法。實現方式是,在組件內部維護一個定時器,實現了組件更新銷毀時的計時器更新銷毀操作,可以認為這種定時器的生命周期綁定了組件的生命周期,不用擔心銷毀和更新的問題。 1. 引言 React PowerPlug 是利用 render props 進行更好狀態管理的工具庫。 React 項目中,一般一個文件就是一個類,...
摘要:作為聲明式的框架,接管了所有頁面更新相關的操作。是用于內部操作的實例,這里將它的初始化為空數組并插入一個新的。連續次后,期望的結果應該是。原因很簡單,因為次的時候,取到的都是在完后不會同步更新。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,導致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非常艱辛的過程...
摘要:回調函數將在更新時觸發,回調中的起到了新的的作用。注冊回調在中使用注冊的回調函數,最終放在模塊的回調函數數組中。 原文地址:https://github.com/joeyguo/blog/issues/2 在單頁應用上,前端路由并不陌生。很多前端框架也會有獨立開發或推薦配套使用的路由系統。那么,當我們在談前端路由的時候,還可以談些什么?本文將簡要分析并實現一個的前端路由,并對 reac...
閱讀 3689·2021-10-13 09:40
閱讀 3149·2021-10-09 09:53
閱讀 3551·2021-09-26 09:46
閱讀 1849·2021-09-08 09:36
閱讀 4248·2021-09-02 09:46
閱讀 1314·2019-08-30 15:54
閱讀 3179·2019-08-30 15:44
閱讀 1023·2019-08-30 11:06