Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Example Analysis of setState Source Code in React

2025-04-02 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article will explain in detail the example analysis of setState source code in React. The editor thinks it is very practical, so I share it with you for reference. I hope you can get something after reading this article.

React as a front-end framework, although it is only the View part of focus in MVVM, it still implements the binding of View and model. While modifying the data, you can refresh the View. This greatly simplifies our logic, only cares about the changes in the data flow, and reduces the amount of code, which makes later maintenance more convenient. This feature is attributed to the setState () method. Queue mechanism is used to manage state in React, which avoids a lot of repeated View refresh. Let's explore the setState mechanism from the point of view of source code.

1 or declare a component first, and find the source step by step from the very beginning

Class App extends Component {/ / execute constructor (props) {super (props) only once when the component is reloaded; / /. } / / other methods} / / ReactBaseClasses.js as follows: here is the source of the setState function; / / super is actually the following function 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');}}

So it mainly depends on whether the updater parameter is passed, that is, when the new component is performed, how the specific updater parameter is passed in, and which object it is, see

React source code analysis series of articles on how context updater is passed in react below

To directly say the result here, the updater object is actually the ReactUpdateQueue object leaked out of ReactUpdateQueue.js.

2 now that we have found the action performed after setState, we are going deep into it step by step

Class Root extends React.Component {constructor (props) {super (props); this.state = {count: 0};} componentDidMount () {let me = this; me.setState ({count: me.state.count + 1}); console.log (me.state.count); / / print out 0 me.setState ({count: me.state.count + 1}); console.log (me.state.count) / print out 0 setTimeout (function () {me.setState ({count: me.state.count + 1}); console.log (me.state.count); / / print out 2}, 0); setTimeout (function () {me.setState ({count: me.state.count + 1}); console.log (me.state.count); / / print out 3}, 0) } render () {return ({this.state.count})} ReactComponent.prototype.setState = function (partialState, callback) {this.updater.enqueueSetState (this, partialState); if (callback) {this.updater.enqueueCallback (this, callback, 'setState');}}

ReactUpdateQueue.js

Var ReactUpdates = require ('. / ReactUpdates'); function enqueueUpdate (internalInstance) {ReactUpdates.enqueueUpdate (internalInstance);}; function getInternalInstanceReadyForUpdate (publicInstance, callerName) {/ / has such a line of code in ReactCompositeComponent.js, which is its source; / / Store a reference from the instance back to the internal representation / / ReactInstanceMap.set (inst, this); var internalInstance = ReactInstanceMap.get (publicInstance); / / returns the object returned by the construct function in ReactCompositeComponent.js The ReactInstance instance object is not a simple new instance object of the component we wrote, but an object wrapped by the ReactCompositeComponentWrapper function in instantiateReactComponent.js. For more information, please see how to create React components and source code analysis .md return internalInstance;}; var ReactUpdateQueue = {/ /. Omit the other code enqueueCallback: function (publicInstance, callback, callerName) {ReactUpdateQueue.validateCallback (callback, callerName); var internalInstance = getInternalInstanceReadyForUpdate (publicInstance); if (! internalInstance) {return null;} / / put callback in the _ pendingCallbacks array of the component instance; if (internalInstance._pendingCallbacks) {internalInstance._pendingCallbacks.push (callback);} else {internalInstance._pendingCallbacks = [callback];} / / TODO: The callback here is ignored when setState is called from / / componentWillMount. Either fix it or disallow doing so completely in / / favor of getInitialState. Alternatively, we can disallow / / componentWillMount during server-side rendering. EnqueueUpdate (internalInstance);}, enqueueSetState: function (publicInstance, partialState) {var internalInstance = getInternalInstanceReadyForUpdate (publicInstance, 'setState'); if (! internalInstance) {return;} / / here, initialize the queue variable and initialize internalInstance._pendingStateQueue = []; / / for the short-circuit operation of | / / queue array (analog queue), you still need to comb through the objects stored in setState. Var queue = internalInstance._pendingStateQueue | | (internalInstance._pendingStateQueue = []); / / put the partialState in the queue array, that is, the internalInstance._pendingStateQueue array. At this point, the partialState of each setState is put into the _ pendingStateQueue attribute on the React component instance object to become an array; queue.push (partialState); enqueueUpdate (internalInstance);},}; module.exports = ReactUpdateQueue

You can see that enqueueSetState enqueueCallback finally executes enqueueUpdate.

Function enqueueUpdate (internalInstance) {ReactUpdates.enqueueUpdate (internalInstance);}

ReactUpdates.js

Var dirtyComponents = []; var updateBatchNumber = 0 null;// var asapCallbackQueue = CallbackQueue.getPooled (); var asapEnqueued = false;// declare batchingStrategy as null here and assign it later through registration; the component parameter here is the React component instance object wrapped by the ReactCompositeComponentWrapper function in js Function enqueueUpdate (component) {ensureInjected (); / / when you execute setState for the first time, you can enter the if statement, encounter the return statement in it, and terminate execution / / if you are not in the stage of creating or updating components, then handle the update transaction if (! batchingStrategy.isBatchingUpdates) {/ / batchedUpdates is the batchingStrategy.batchedUpdates (enqueueUpdate, component) declared in ReactDefaultBatchingStrategy.js; return } / / when you execute setState for the second time, you cannot enter the if statement and put the component into dirtyComponents / / if you are creating or updating a component, do not deal with update for the time being, but just put the component in the dirtyComponents array dirtyComponents.push (component); if (component._updateBatchNumber = = null) {component._updateBatchNumber = updateBatchNumber + 1;}}; / / enqueueUpdate contains React's logic to avoid repeating render. At the beginning of execution, the mountComponent and updateComponent methods call batchedUpdates for batch updates, where isBatchingUpdates is set to true, that is, the status is marked as being in the update phase. After that, React handles the component update in a transactional manner, and wrapper.close () is called after the transaction, and TRANSACTION_WRAPPERS contains the wrapper of RESET_BATCHED_UPDATES, so RESET_BATCHED_UPDATES.close () is eventually called, which eventually sets isBatchingUpdates to false.

ReactDefaultBatchingStrategy.js

/ / RESET_BATCHED_UPDATES is used to manage the status of isBatchingUpdates var RESET_BATCHED_UPDATES = {initialize: emptyFunction, close: function () {/ / when the transaction batch update process ends, set isBatchingUpdates to false ReactDefaultBatchingStrategy.isBatchingUpdates = false;}}; / / FLUSH_BATCHED_UPDATES runs runBatchedUpdates during the close phase of transaction, thus executing update. / / because the execution order of close is FLUSH_BATCHED_UPDATES.close = = > and then RESET_BATCHED_UPDATES.closevar 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;}}) / / this transition is the transaction variable var transaction = new ReactDefaultBatchingStrategyTransaction () used in the following ReactDefaultBatchingStrategy object; var ReactDefaultBatchingStrategy = {isBatchingUpdates: false, / * * Call the provided function in a context within which calls to `setState` * and friends are batched such that components aren't updated unnecessarily. * / batchedUpdates: function (callback, a, b, c, d, e) {var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;// batch initially set isBatchingUpdates to true, indicating that ReactDefaultBatchingStrategy.isBatchingUpdates = true; / / The code is written this way to avoid extra allocations if (alreadyBatchingUpdates) {return callback (a, b, c, d, e) {return callback (a, b, c, d, e);} else {/ / transition has been declared above / / handle updates in a transactional manner. Later, transaction return transaction.perform (callback, null, a, b, c, d, e);}; module.exports = ReactDefaultBatchingStrategy

Next, let's take a look at how the transaction handling mechanism in React works.

Transaction.js

Var _ prodInvariant = require ('. / reactProdInvariant'); var invariant = require ('fbjs/lib/invariant'); var OBSERVED_ERROR = {}; var TransactionImpl = {reinitializeTransaction: function () {/ / getTransactionWrappers, declared in the function ReactDefaultBatchingStrategy.js, with; returns an array This.transactionWrappers = this.getTransactionWrappers (); if (this.wrapperInitData) {this.wrapperInitData.length = 0;} else {this.wrapperInitData = [];} this._isInTransaction = false;}, _ isInTransaction: false, getTransactionWrappers: null, isInTransaction: function () {return!! this._isInTransaction;}, perform: function (method, scope, a, b, c, d, e, f) {var errorThrown; var ret; try {this._isInTransaction = true; errorThrown = true / / var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]; / 1 the initialize methods of all members of TRANSACTION_WRAPPERS are executed first, which are all declared as emptyFunction this.initializeAll (0); / / 2 the enqueueUpdate function ret = method.call (scope, a, b, c, d, e, f) is actually executed here; errorThrown = false } finally {try {if (errorThrown) {/ / If `method`throws, prefer to show that stack trace over any thrown / / by invoking `closeAll`. Try {this.closeAll (0);} catch (err) {}} else {/ / Since `method`didn't throw, we don't want to silence the exception / / here. / / 3 execute all close methods of members in the TRANSACTION_WRAPPERS object; this.closeAll (0);}} finally {this._isInTransaction = false;}} return ret;}, initializeAll: function (startIndex) {var transactionWrappers = this.transactionWrappers; for (var I = startIndex; I)

< transactionWrappers.length; i++) { var wrapper = transactionWrappers[i]; try { this.wrapperInitData[i] = OBSERVED_ERROR; this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null; } finally { if (this.wrapperInitData[i] === OBSERVED_ERROR) { try { this.initializeAll(i + 1); } catch (err) {} } } } }, closeAll: function (startIndex) { var transactionWrappers = this.transactionWrappers; for (var i = startIndex; i < transactionWrappers.length; i++) { var wrapper = transactionWrappers[i]; var initData = this.wrapperInitData[i]; var errorThrown; try { errorThrown = true; if (initData !== OBSERVED_ERROR && wrapper.close) { wrapper.close.call(this, initData); } errorThrown = false; } finally { if (errorThrown) { try { this.closeAll(i + 1); } catch (e) {} } } } this.wrapperInitData.length = 0; }};module.exports = TransactionImpl//3 执行TRANSACTION_WRAPPERS对象中成员的所有close方法;var FLUSH_BATCHED_UPDATES = { initialize: emptyFunction, close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)}; 接着会执行ReactUpdates.js中的flushBatchedUpdates方法 ReactUpdates.js中 var flushBatchedUpdates = function () { while (dirtyComponents.length || asapEnqueued) { if (dirtyComponents.length) { var transaction = ReactUpdatesFlushTransaction.getPooled(); //这里执行runBatchedUpdates函数; transaction.perform(runBatchedUpdates, null, transaction); ReactUpdatesFlushTransaction.release(transaction); } if (asapEnqueued) { asapEnqueued = false; var queue = asapCallbackQueue; asapCallbackQueue = CallbackQueue.getPooled(); queue.notifyAll(); CallbackQueue.release(queue); } }};function runBatchedUpdates(transaction) { var len = transaction.dirtyComponentsLength; dirtyComponents.sort(mountOrderComparator); updateBatchNumber++; for (var i = 0; i < len; i++) { var component = dirtyComponents[i]; var callbacks = component._pendingCallbacks; component._pendingCallbacks = null; var markerName; if (ReactFeatureFlags.logTopLevelRenders) { var namedComponent = component; // Duck type TopLevelWrapper. This is probably always true. if (component._currentElement.type.isReactTopLevelWrapper) { namedComponent = component._renderedComponent; } markerName = 'React update: ' + namedComponent.getName(); console.time(markerName); }//这里才是真正的开始更新组件 ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber); if (markerName) { console.timeEnd(markerName); } if (callbacks) { for (var j = 0; j < callbacks.length; j++) { transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance()); } } }} ReactReconciler.js中 performUpdateIfNecessary: function (internalInstance, transaction, updateBatchNumber) { if (internalInstance._updateBatchNumber !== updateBatchNumber) { // The component's enqueued batch number should always be the current // batch or the following one. return; } //这里执行React组件实例对象的更新;internalInstance上的performUpdateIfNecessary在ReactCompositeComponent.js中的; internalInstance.performUpdateIfNecessary(transaction); if (process.env.NODE_ENV !== 'production') { if (internalInstance._debugID !== 0) { ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID); } } } ReactCompositeComponent.js performUpdateIfNecessary: function (transaction) { if (this._pendingElement != null) { // receiveComponent会最终调用到updateComponent,从而刷新View ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context); } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) { // 执行updateComponent,从而刷新View。 this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context); } else { this._updateBatchNumber = null; }}, //执行更新React组件的props. state。context函数 updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) { var inst = this._instance; var willReceive = false; var nextContext; // Determine if the context has changed or not if (this._context === nextUnmaskedContext) { nextContext = inst.context; } else { nextContext = this._processContext(nextUnmaskedContext); willReceive = true; } var prevProps = prevParentElement.props; var nextProps = nextParentElement.props; // Not a simple state update but a props update if (prevParentElement !== nextParentElement) { willReceive = true; } // An update here will schedule an update but immediately set // _pendingStateQueue which will ensure that any state updates gets // immediately reconciled instead of waiting for the next batch. if (willReceive && inst.componentWillReceiveProps) { if (process.env.NODE_ENV !== 'production') { measureLifeCyclePerf(function () { return inst.componentWillReceiveProps(nextProps, nextContext); }, this._debugID, 'componentWillReceiveProps'); } else { inst.componentWillReceiveProps(nextProps, nextContext); } }//这里可以知道为什么setState可以接受函数,主要就是_processPendingState函数; //这里仅仅是将每次setState放入到_pendingStateQueue队列中的值,合并到nextState,并没有真正的更新state的值;真正更新组件的state的值是在下面; var nextState = this._processPendingState(nextProps, nextContext); var shouldUpdate = true; if (!this._pendingForceUpdate) { if (inst.shouldComponentUpdate) { if (process.env.NODE_ENV !== 'production') { shouldUpdate = measureLifeCyclePerf(function () { return inst.shouldComponentUpdate(nextProps, nextState, nextContext); }, this._debugID, 'shouldComponentUpdate'); } else { shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext); } } else { if (this._compositeType === CompositeTypes.PureClass) { shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState); } } } this._updateBatchNumber = null; if (shouldUpdate) { this._pendingForceUpdate = false; // Will set `this.props`, `this.state` and `this.context`. this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext); } else { // If it's determined that a component should not update, we still want // to set props and state but we shortcut the rest of the update. //诺:在这里更新组件的state. props 等值; this._currentElement = nextParentElement; this._context = nextUnmaskedContext; inst.props = nextProps; inst.state = nextState; inst.context = nextContext; } },_processPendingState: function (props, context) { var inst = this._instance; var queue = this._pendingStateQueue; var replace = this._pendingReplaceState; this._pendingReplaceState = false; this._pendingStateQueue = null; if (!queue) { return inst.state; } if (replace && queue.length === 1) { return queue[0]; } var nextState = _assign({}, replace ? queue[0] : inst.state); for (var i = replace ? 1 : 0; i < queue.length; i++) { var partial = queue[i]; //如果是setState的参数是一个函数,那么该函数接受三个参数,分别是state props context _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial); } return nextState;}, this.state的更新会在_processPendingState执行完执行。所以两次setState取到的都是this.state.count最初的值0,这就解释了之前的现象。其实,这也是React为了解决这种前后state依赖但是state又没及时更新的一种方案,因此在使用时大家要根据实际情况来判断该用哪种方式传参。来看个小例子直观感受下 handleClickOnLikeButton () { this.setState({ count: 0 }) // =>

This.state.count or undefined this.setState ({count: this.state.count + 1}) / / = > undefined + 1 = NaN this.setState ({count: this.state.count + 2}) / / = > NaN + 2 = NaN} / /.... VS.... handleClickOnLikeButton () {this.setState ((prevState) = > {return {count: 0}}) this.setState (prevState) = > {return {count: prevState.count + 1} / / the return of the previous setState is count 0 Currently returns 1}) this.setState ((prevState) = > {return {count: prevState.count + 2} / / the last setState returns count 1, currently returns 3}) / / the final result is this.state.count 3}.

The setState process is complex and delicately designed to avoid repetitive unnecessary refresh components. Its main process is as follows

EnqueueSetState puts state in the queue and calls enqueueUpdate to process the Component to be updated

If the component is currently in a update transaction, store the Component in dirtyComponent first. Otherwise, batchedUpdates processing is called.

BatchedUpdates initiates a transaction.perform () transaction

Start and execute three phases of transaction initialization, run, and end

Initialization: there is no registered method in the transaction initialization phase, so there is no method to execute

Run: the callback method passed when setSate is executed. Generally, the callback parameter is not passed.

End: update isBatchingUpdates to false and execute the close method in FLUSH_BATCHED_UPDATES, wrapper

During the close phase, FLUSH_BATCHED_UPDATES iterates through all the dirtyComponents, calls the updateComponent refresh component, and executes its pendingCallbacks, which is the callback set in setState.

This is the end of this article on "sample analysis of setState source code in React". I hope the above content can be of some help to you, so that you can learn more knowledge. if you think the article is good, please share it out for more people to see.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report