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:
- one is to update the component by executing the
updateComponent
method -
second, if the callback function is passed in the
setState
method, the
callback function is stored in thecallbackQueue
queue.
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:
- get old component information
- get new component information
-
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. should
function returnstrue
to update the old component.-
should
function returnsfalse
to uninstall old components and
mount new components.
The
The
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.