I Love ReactJS

Analysis of React source code (3): detailed explanation of transaction and update queue

in the previous two articles, we analyzed the implementation, mounting, and lifecycle processes
of React components. In reading the source code, we often see code such as
transaction and UpdateQueue , which involves two concepts in React:
transactions and update queues. Because we have covered all of these in the previous article,
this article explores the transaction mechanism and update queues based on the all-familiar
setState method.

1.setState related

in the first article
Analysis of React Source Code (1): implementation and mounting of components

we already know The component prototype declared by class has the
setState method:

this method passes in two parameters partialState and callBack ,
the former is the new state value, and the latter is the callback function.
updater is defined in the constructor:

you can see that updater is passed in by the constructor, so if you find out
where new ReactComponent is executed, you can find out what
updater is. Taking the custom component ReactCompositeComponent as
an example, we found its trail in the _ constructComponentWithoutOwner method:

return new Component(publicProps, publicContext, updateQueue);
undefined

the corresponding parameter updater is actually updateQueue . Let's
look at what enqueueSetState is in this.updater.enqueueSetState :

The purpose of the

getInternalInstanceReadyForUpdate method is to get the current component object
and assign it to the internalInstance variable. Next, determine whether the state
update queue for the current component object exists, and if so, add the
partialState , that is, the new state value, to the queue; if not, create an
update queue for the object, and notice that the queue exists as an array. Let's look at what
the last called enqueueUpdate method did:

is visible from the code. When batchingStrategy.isBatchingUpdates is
false , the batchedUpdates update queue is executed, and if
true , the component is placed in dirtyComponent . Let's look at the
source code of batchingStrategy :

roughly speaking, the initial value of isBatchingUpdates is false , and
batchedUpdates executes the callback function passed in internally.

it seems a little confusing to see such a long logic, but from this code we vaguely realize that
React does not casually update components, but executes it through the judgment of state (as if
true/false). In fact, the concept of a "state machine" is adopted within React, and the logic is
not the same when components are in different states. Taking the component update process as an
example, React updates the component in the form of transaction + state, so let's explore the
mechanism of transaction.

2.transaction transaction

first of all, take a look at the analytical chart of the official source code:

<pre>
 *                       wrappers (injected at creation time)
 *                                      +        +
 *                                      |        |
 *                    +-----------------|--------|--------------+
 *                    |                 v        |              |
 *                    |      +---------------+   |              |
 *                    |   +--|    wrapper1   |---|----+         |
 *                    |   |  +---------------+   v    |         |
 *                    |   |          +-------------+  |         |
 *                    |   |     +----|   wrapper2  |--------+   |
 *                    |   |     |    +-------------+  |     |   |
 *                    |   |     |                     |     |   |
 *                    |   v     v                     v     v   | wrapper
 *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
 * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
 * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | +---+ +---+   +---------+   +---+ +---+ |
 *                    |  initialize                    close    |
 *                    +-----------------------------------------+
 * </pre>
undefined

it is simple from the flow chart that each method is wrapped by wrapper and must
be called with perform to execute initialize and
close respectively before and after the wrapped method. Give an example of the
difference in execution between an ordinary function and a function wrapped in
wrapper :

function method(){
    console.log('111')
};
transaction.perform(method);
//执行initialize方法
//输出'111'
//执行close方法
undefined

We know that transaction.perform (callBack) actually calls
transaction.perform (enqueueUpdate) in the previous
batchingStrategy code, but there is still
transaction.perform (enqueueUpdate) in the enqueueUpdate method.
Doesn't that create a dead loop?

the effect of wrapper is apparent in order to avoid the problem of a possible
endless loop. Let's look at how these two wrapper are defined:

from the mind map above, we can see that the initial value of
isBatchingUpdates is false . When
transaction.perform (enqueueUpdate) is executed in the form of a transaction, the
actual execution process is as follows:

// RESET_BATCHED_UPDATES.initialize() 实际为空函数
// enqueue()
// RESET_BATCHED_UPDATES.close()
undefined

if described in words, that is, RESET_BATCHED_UPDATES this
wrapper is used to set isBatchingUpdates , that is, the value of
the update status of the component. If the component has update requirements, it will be set to
the update status, and then return to the original state after the update.

what are the benefits of this? Of course, it is to avoid repeated render of components and
improve performance.

RESET_BATCHED_UPDATES is used to change the Boolean value of
isBatchingUpdates false or true , so what is the
purpose of FLUSH_BATCHED_UPDATES ? In fact, you can roughly guess that its
function is to update components. First, take a look at the implementation logic of
FLUSH_BATCHED_UPDATES.close () :

you can see that the flushBatchedUpdates method iterates through all the
dirtyComponents methods, and then calls the
runBatchedUpdates method in the form of a transaction. Because the source code is
long, the two things that the method does are directly explained here:

take a look at updateComponent Source Code:

you can see that the componentWillReceiveProps method and the
shouldComponentUpdate method are executed. One thing that can't be ignored is
that the _ processPendingState method was executed before
shouldComponentUpdate . Let's see what this function does:

this function mainly deals with state :
1. If the update team is listed as null , then return the original
state ;
two。 If there is an update in the update queue, the update value is returned;
3. If the update queue has more than one update, merge them through the for loop;
To sum up, during a life cycle, all state changes are merged and finally
processed uniformly before state is executed.

go back to _ updateComponent . Finally, if shouldUpdate is
true , execute _ performComponentUpdate method:

After a quick glance at

, you will find the same pattern. Execute the componentWillUpdate lifecycle
method, and execute the componentDidUpdate method after the update is completed.
Let's take a look at the _ updateRenderedComponent method responsible for
updating:

the idea of this code is clear:

  1. get old component information
  2. get new component information
  3. shouldUpdateReactComponent is a method (hereinafter referred to as
    should function) that determines whether to update or not based on the passed
    information of new and old components.
  4. The

  5. should function returns true to update the old component.
  6. The

  7. should function returns false to uninstall old components and
    mount new components.

4. Write at the end

(1) setState callback function

The process of

setState callback function is similar to that of state .
state is handled by enqueueSetState , and the callback function is
handled by enqueueCallback . Interested readers can explore for themselves.

(2) about crashes caused by setState

We already know that this.setState actually calls enqueueSetState .
When the component is updated, because the new state has not been merged,
this._pendingStateQueue is true :

in the following performUpdateIfNecessary code.

and after merging state , React will set this._pendingStateQueue to
null , so that when dirtyComponent enters the next batch
processing, the updated components will not enter a repetitive process, ensuring that the
components will only update once.

so the reason why setState cannot be called in
componentWillUpdate is that setState will make
_ pendingStateQueue true , causing
updateComponent to be executed again, and then
componentWillUpdate will be called again, and finally
componentWillUpdate will be called in a loop.

(3) about React dependency injection

in the previous code, for the update queue flag batchingStrategy , we directly
turned to ReactDefaultBatchingStrategy for analysis, because there is a lot of
dependency injection within React. During React initialization,
ReactDefaultInjection.js is injected into ReactUpdates as the
default strategy. Dependency injection has a large number of applications in server-side
rendering of React, which can be explored by interested students.

Exit mobile version