Preface
Today we come to an unusual advanced React article. In this article, we use some unusual phenomena to analyze the causes and find the results through the process of investigating, so as to understand React, walk into the world of React, and unveil the veil of React. , I am convinced that a can be used better.
I admit that this name may be a bit of a headline. The inspiration comes from a column called "Into Science" on CCTV when I was a child. It introduced various supernatural and supernatural phenomena every day, and it was very magical. When the secret was finally revealed, it turned out to be a variety of pediatrics. Question, I think it’s funny now 😂😂. But the nature of these React'supernatural' phenomena I introduced today is not pediatric. After each phenomenon, the React operating mechanism and design principles are revealed. (The react version we are talking about is 16.13.1
)
Okay, let’s not talk too much nonsense, my big detectives, are you ready? Let’s start today's journey of revealing the secrets.
Case 1: The component is repeatedly mounted inexplicably
Received a report
A previous classmate encountered a weird situation. He hoped componentDidUpdate
executed. The source of the component update comes from the change props
But the parent component changed to props
found that the view was rendered, but componentDidUpdate
not executed. What is even more weird is that componentDidMount
executed. code show as below:
// TODO: 重复挂载
class Index extends React.Component{
componentDidMount(){
console.log('组件初始化挂载')
}
componentDidUpdate(){
console.log('组件更新')
/* 想要做一些事情 */
}
render(){
return <div>《React进阶实践指南》 👍 { this.props.number } + </div>
}
}
effect of
componentDidUpdate
not executed and componentDidMount
executed, indicating that the component did not update the logic , but went and repeatedly mounted .
Check one by one
The child component is at a loss, and we don't find the reason at all, we have to start with the parent component. Let's take a look at how the parent component is written.
const BoxStyle = ({ children })=><div className='card' >{ children }</div>
export default function Home(){
const [ number , setNumber ] = useState(0)
const NewIndex = () => <BoxStyle><Index number={number} /></BoxStyle>
return <div>
<NewIndex />
<button onClick={ ()=>setNumber(number+1) } >点赞</button>
</div>
}
Found some clues from the parent component. In the parent component, first use BoxStyle
as a container component, add styles, and render our child component Index
, but each time we combine the container components to form a new component NewIndex
, the real mount is NewIndex
, the truth is out of the question.
Precautions
The essence of this situation is that every render
process, a new component is formed. For new components, React's processing logic is to directly uninstall the old components and remount the new components. Therefore, we should pay attention to a problem in the development process. Is:
- For functional components, do not declare and render new components in their function execution context, so that each function update will cause the component to be repeatedly mounted.
- For class components, do not
render
function, otherwise the subcomponents will be repeatedly mounted.
Case 2: The source of the incident e.target is strangely missing
Emergencies
The pseudonym (Xiao Ming) had a whim to write a controlled component on a black and windy night. What is written is as follows:
export default class EventDemo extends React.Component{
constructor(props){
super(props)
this.state={
value:''
}
}
handerChange(e){
setTimeout(()=>{
this.setState({
value:e.target.value
})
},0)
}
render(){
return <div>
<input placeholder="请输入用户名?" onChange={ this.handerChange.bind(this) } />
</div>
}
}
input
value by state
in value
property control, Xiao Ming wanted by handerChange
change value
value, but he expects setTimeout
complete update. But when he wants to change the input value, unexpected things happen.
The console error is shown above. Cannot read property 'value' of null
means that e.target
is null
. Event source target
how can I say nothing?
Clue tracking
Upon receipt of this case, we first troubleshoot the problem, then we first handerChange
direct printing e.target
, as follows:
It seems that the first investigation is not handerChange
, and then we setTimeout
and find:
Sure enough, it was setTimeout
. Why did the event source e.target in setTimeout
First of all, the event source is definitely not missing inexplicably. It is certain that the bottom layer of React has done some additional processing on the event source. First of all, we know that React uses the event synthesis mechanism, that is, the bound onChange
is not the real bound change
event handerChange
bound by Xiao Ming is not a real event handling function either. So in other words, the bottom layer of React helps us deal with the event source. All this may be the only way for us to find clues from the React source code. After investigating the source code, I found a very suspicious clue.
react-dom/src/events/DOMLegacyEventPluginSystem.js
function dispatchEventForLegacyPluginEventSystem(topLevelType,eventSystemFlags,nativeEvent,targetInst){
const bookKeeping = getTopLevelCallbackBookKeeping(topLevelType,nativeEvent,targetInst,eventSystemFlags);
batchedEventUpdates(handleTopLevel, bookKeeping);
}
dispatchEventForLegacyPluginEventSystem
is the main function that all events must pass through in the legacy
batchedEventUpdates
is the logic for processing batch updates, which will execute our real event processing functions. We have nativeEvent
in the event principle chapter that true native event object of event
. targetInst
is e.target
corresponding fiber
object. The event source we obtained in handerChange
is the event source synthesized by React. Then know when and how the event source is synthesized? This may be helpful for solving the case.
In the event principle chapter, we will introduce the event plug-in mechanism used by React. For example, our onClick event corresponds to SimpleEventPlugin
. Then Xiaoming writes onChange
also has a special ChangeEventPlugin
event plug-in. These plug-ins have a vital role to synthesize our event source object e , So let’s take a look at ChangeEventPlugin
.
react-dom/src/events/ChangeEventPlugin.js
const ChangeEventPlugin ={
eventTypes: eventTypes,
extractEvents:function(){
const event = SyntheticEvent.getPooled(
eventTypes.change,
inst, // 组件实例
nativeEvent, // 原生的事件源 e
target, // 原生的e.target
);
accumulateTwoPhaseListeners(event); // 这个函数按照冒泡捕获逻辑处理真正的事件函数,也就是 handerChange 事件
return event; //
}
}
We see that the e in the event source handerChange
of the synthetic event is created by SyntheticEvent.getPooled
So this is the key to solving the case.
legacy-events/SyntheticEvent.js
SyntheticEvent.getPooled = function(){
const EventConstructor = this; // SyntheticEvent
if (EventConstructor.eventPool.length) {
const instance = EventConstructor.eventPool.pop();
EventConstructor.call(instance,dispatchConfig,targetInst,nativeEvent,nativeInst,);
return instance;
}
return new EventConstructor(dispatchConfig,targetInst,nativeEvent,nativeInst,);
}
Fanwai: In the chapter on the event system, the article's thoughts on the event pool are hurried and general. This section will supplement the thoughts on the event pool in detail. <br/>
getPooled
introduces the real concept of event pool, it mainly does two things:
- Determine whether there are free event sources in the event pool, and if there are any event sources to be reused.
- If not, create a new event source object by way of
new SyntheticEvent
ThenSyntheticEvent
is the constructor to create the event source object, let's study it together.
const EventInterface = {
type: null,
target: null,
currentTarget: function() {
return null;
},
eventPhase: null,
...
};
function SyntheticEvent( dispatchConfig,targetInst,nativeEvent,nativeEventTarget){
this.dispatchConfig = dispatchConfig;
this._targetInst = targetInst; // 组件对应fiber。
this.nativeEvent = nativeEvent; // 原生事件源。
this._dispatchListeners = null; // 存放所有的事件监听器函数。
for (const propName in Interface) {
if (propName === 'target') {
this.target = nativeEventTarget; // 我们真正打印的 target 是在这里
} else {
this[propName] = nativeEvent[propName];
}
}
}
SyntheticEvent.prototype.preventDefault = function (){ /* .... */ } /* 组件浏览器默认行为 */
SyntheticEvent.prototype.stopPropagation = function () { /* .... */ } /* 阻止事件冒泡 */
SyntheticEvent.prototype.destructor = function (){ /* 情况事件源对象*/
for (const propName in Interface) {
this[propName] = null
}
this.dispatchConfig = null;
this._targetInst = null;
this.nativeEvent = null;
}
const EVENT_POOL_SIZE = 10; /* 最大事件池数量 */
SyntheticEvent.eventPool = [] /* 绑定事件池 */
SyntheticEvent.release=function (){ /* 清空事件源对象,如果没有超过事件池上限,那么放回事件池 */
const EventConstructor = this;
event.destructor();
if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
EventConstructor.eventPool.push(event);
}
}
After I refined this piece of code, the truth gradually emerged. Let’s take a look at what SyntheticEvent
did:
- First give some initialized variables
nativeEvent
and so on. Then follow theEventInterface
rule to copy the attributes on the native event source React event source. Then an important thing is that the e.target we printed is this.target, which is bound to the reale.target->nativeEventTarget
- Then the React event source binds its own blocking default behavior
preventDefault
, blocking bubblingstopPropagation
and other methods. But here is a key method,destructor
, This function nulls React's own event source object. Then we finally found the answer. The high probability of our event source e.target disappearing is becausedestructor
anddestructor
were triggered inrelease
, and then the event source was put into the event pool, waiting for the next reuse.
Now all the spearheads are directed at release
, release
be triggered?
legacy-events/SyntheticEvent.js
function executeDispatchesAndRelease(){
event.constructor.release(event);
}
When the React event system finishes executing all _dispatchListeners
, it will trigger this method executeDispatchesAndRelease
release the current event source.
The truth is revealed
Back to the problem encountered by Xiaoming, we mentioned above that React will finally empty the event source synchronously and put it into the event pool, because setTimeout
is executed asynchronously, and the event source object has been reset and released during execution. , So we print e.target = null
, so far, the truth of the case is revealed.
Through this case, we understand some concepts of React event pool:
- The React event system has unique synthetic events, its own event source, and processing logic for some special situations, such as bubbling logic.
- In order to prevent each event from creating an event source object and wasting performance, React introduces the event pool concept . Every user event will take an e from the event pool. If not, create one, then assign the event source, and wait until After the event is executed, reset the event source and put it back into the event pool to achieve reuse.
represented by a flow chart:
Case 3: True and False React
Crime scene
This is what happened to the author. When I was developing a React project, I uploaded some encapsulated custom Hooks to the company’s private package management platform for logic reuse. When developing another React project, Download the company's package and use it inside the component. code show as below:
function Index({classes, onSubmit, isUpgrade}) {
/* useFormQueryChange 是笔者写好的自定义hooks,并上传到私有库,主要是用于对表单控件的统一管理 */
const {setFormItem, reset, formData} = useFormQueryChange()
React.useEffect(() => {
if (isUpgrade) reset()
}, [ isUpgrade ])
return <form
className={classes.bootstrapRoot}
autoComplete='off'
>
<div className='btnbox' >
{ /* 这里是业务逻辑,已经省略 */ }
</div>
</form>
}
useFormQueryChange
is a custom 060bc414f20221 written by the author and uploaded to the private library. It is mainly used for the unified management of form controls. hooks
The error content is as follows:
Check one by one
We follow the content of React's error report to troubleshoot the problems one by one:
- The first possible error reason is
You might have mismatching versions of React and the renderer (such as React DOM)
, which means that theReact
andReact Dom
inconsistent, causing this situation, but theReact
andReact Dom
in our project are bothv16.13.1
, so we rule out this suspicion. - The second possible error reason
You might be breaking the Rules of Hooks
means that you broke the Hooks rule. This situation is also impossible, because the author's code does not break thehoos
rule. So also rule out suspicion. - The third possible error reason
You might have more than one copy of React in the same app
means that there may be multiple Reacts in the same application. So far, all the suspects point to the third one. First of all, the custom hooks we referenced, will there be a React inside?
I follow the above tips to troubleshoot custom hooks corresponding node_modules
Sure enough there is another React, is this false React (let's call them false React) out of the ghost. As we in the 160bc414f2033d Hooks Principle article,
React Hooks
initialized with ReactCurrentDispatcher.current
in the component, and different hooks objects are assigned in the component update stage. After the update is completed, ContextOnlyDispatcher
assigned. If the hooks under this object are called, the above error will be reported, so it is explained. this error is because of our project, the React introduced in the execution context is the React of the project itself, but the custom Hooks refer to ContextOnlyDispatcher
Next I saw package.json
in the component library,
"dependencies": {
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
It turns out that React is used as dependencies
so when downloading custom Hooks
, React
downloaded 060bc414f2039e again. So how to solve this problem. React for packaging component library, library hooks, can not be used dependencies
, because it is the current dependencies
is downloaded to the custom hooks dependent library following node_modules
in. Instead, you should use peerDependencies
, use peerDependencies
, customize hooks
then find related dependencies, you will go to node_modules
of our project to find it, which can fundamentally solve this problem.
So we changed
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8",
},
It solves this problem perfectly.
Clear the fog
This question makes us understand the following:
- For some hooks libraries, component libraries, and their own dependencies, they already exist in the project, so declare them
peerDependencies
- In the development process, different versions of the same dependency are likely to be used. For example, the project introduces a dependency of version A, and the component library introduces a dependency of version B. So how to deal with this situation. A resolutions configuration item is provided in the
package.json
resolutions
, so that it will not cause the problem caused by the dependency of multiple versions of the project.
Project package.json
writes like this
{
"resolutions": {
"react": "16.13.1",
"react-dom": "16.13.1"
},
}
In this way, regardless of the dependencies in the project or the dependencies in other libraries, a unified version will be used, which fundamentally solves the problem of multiple versions.
Case 4: PureComponet/memo function failure problem
Case description
When developing React, we wanted to use PureComponent
for performance optimization and adjust component rendering, but after writing a piece of code, we found that the PureComponent
actually failed. The specific code is as follows:
class Index extends React.PureComponent{
render(){
console.log('组件渲染')
const { name , type } = this.props
return <div>
hello , my name is { name }
let us learn { type }
</div>
}
}
export default function Home (){
const [ number , setNumber ] = React.useState(0)
const [ type , setType ] = React.useState('react')
const changeName = (name) => {
setType(name)
}
return <div>
<span>{ number }</span><br/>
<button onClick={ ()=> setNumber(number + 1) } >change number</button>
<Index type={type} changeType={ changeName } name="alien" />
</div>
}
We had expected:
- For the Index component, only the
props
andtype
changes inname
cause the component to render. But the actual situation is like this:
Click the button effect:
Come to the bottom
Why does this happen? Let's check the Index
component again and find that there is a changeType
Index
component. Is this the reason? Let's analyze, first of all status updates in the parent component Home
on, Home
component updates occur every time a new changeName
, so Index
of PureComponent
each meeting shallow comparison , found props
in changeName
not equal each time, So it was updated, giving us the intuitive feeling that it is invalid.
So how to solve this problem, React hooks
provided useCallback
, can props
cache incoming callback function, we have to change it Home
code.
const changeName = React.useCallback((name) => {
setType(name)
},[])
effect:
This solves the problem fundamentally. Use useCallback
to changeName
function. Every time the Home
component is executed, as long as the useCallback
in deps
has not changed, the changeName
still points to the original function, so the PureComponent
will find the same changeName
, 414f205f7 and 060bc414f205f7. The component is not rendered, so far the case has been solved.
Keep going
When developing functional components + class components, if you use React.memo React.PureComponent
such as 060bc414f20627, pay attention to the way to bind events to these components. If it is a functional component, you want to continue to maintain the pure component rendering control feature , Then please use useCallback
, useMemo
and other apis. If it is a class component, please do not use arrow functions to bind events. Arrow functions will also cause failure.
The above mentioned a shallow comparison shallowEqual
, then we focus on analyzing how PureComponent
is shallowEqual
, and then we are going to delve into the mystery of shallowEqual
Then it starts with the update of the class rental price.
react-reconciler/src/ReactFiberClassComponent.js
function updateClassInstance(){
const shouldUpdate =
checkHasForceUpdateAfterProcessing() ||
checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
);
return shouldUpdate
}
I simplified updateClassInstance
here, and only kept the part related to PureComponent
. updateClassInstance
is mainly used to execute the life cycle, update the state, and determine whether the component is re-rendered. The returned shouldUpdate
used to determine whether the current class component is rendered. checkHasForceUpdateAfterProcessing
checks whether the source of the update is the same as forceUpdate. If it is the forceUpdate
component, it will definitely be updated. checkShouldComponentUpdate
checks whether the component is rendered. Let's look at the logic of this function next.
function checkShouldComponentUpdate(){
/* 这里会执行类组件的生命周期 shouldComponentUpdate */
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
nextContext,
);
/* 这里判断组件是否是 PureComponent 纯组件,如果是纯组件那么会调用 shallowEqual 浅比较 */
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
}
checkShouldComponentUpdate
has two vital functions:
- The first is if the class component life cycle
shouldComponentUpdate
, will perform lifecycleshouldComponentUpdate
, to determine whether the component is rendered. - If it is found to be a pure component
PureComponent
, it will compare the new and oldprops
andstate
see if they are equal. If they are equal, the component will not be updated.isPureReactComponent
is ourPureComponent
, which proves to be a pure component.
Next is the key point shallowEqual
, take props
as an example, let's take a look.
shared/shallowEqual
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) { // is可以 理解成 objA === objB 那么返回相等
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
} // 如果新老props有一个不为对象,或者不存在,那么直接返回false
const keysA = Object.keys(objA); // 老props / 老state key组成的数组
const keysB = Object.keys(objB); // 新props / 新state key组成的数组
if (keysA.length !== keysB.length) { // 说明props增加或者减少,那么直接返回不想等
return false;
}
for (let i = 0; i < keysA.length; i++) { // 遍历老的props ,发现新的props没有,或者新老props不同等,那么返回不更新组件。
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true; //默认返回相等
}
shallowEqual
process of 060bc414f2077a is like this. shallowEqual
returns true
it proves that they are equal, and then the component is not updated; if it returns false
prove that you do not want to wait, then the component is updated. is
can be understood as ===
- In the first step, directly pass === to judge whether they are equal, if they are equal, then return
true
. Normally, as long as callingReact.createElement
will recreateprops
,props
are not equal. - In the second step, if one of the new and old
props
is not an object, or does not exist, then directly return tofalse
. - The third step is to determine whether the
props
,key
, etc. do not want to wait, indicating thatprops
has increased or decreased, then directly return tofalse
. - The fourth step is to traverse the old
props
and find that the newprops
does not correspond to it, or the new and oldprops
different, then return tofalse
. - By default,
true
returned.
This is the shallowEqual
, and the code is very simple. Interested students can take a look.
Case 5: useState updates the same State, and the function component is executed twice
Received a report
This question is actually very hanging. You may not have noticed it. What caught my attention was a question asked by a friend of the Nuggets. The question is as follows:
First of all thank the report carefully dig Friends, I React-Hooks principle mentioned too, for updated components methods in the component useState
and class components setState
have some differences, useState
the source code if you encounter twice The same state
will prevent the component from updating by default, but setState
is not set in PureComponent
in the class component, the same state
will be updated twice.
Let’s review how hooks
prevents component updates.
react-reconciler/src/ReactFiberHooks.js -> dispatchAction
if (is(eagerState, currentState)) {
return
}
scheduleUpdateOnFiber(fiber, expirationTime); // 调度更新
If a determination on state
-> currentState
, and this time state
-> eagerState
equal, direct return
prevent components scheduleUpdate
schedule update. So we think that if useState
triggers the same state twice, then the component can only be updated once, but is this really the case? .
Investigation
demo
the clues provided by this digger, we began to write 060bc414f20971 for verification.
const Index = () => {
const [ number , setNumber ] = useState(0)
console.log('组件渲染',number)
return <div className="page" >
<div className="content" >
<span>{ number }</span><br/>
<button onClick={ () => setNumber(1) } >将number设置成1</button><br/>
<button onClick={ () => setNumber(2) } >将number设置成2</button><br/>
<button onClick={ () => setNumber(3) } >将number设置成3</button>
</div>
</div>
}
export default class Home extends React.Component{
render(){
return <Index />
}
}
As in the demo above, there are three buttons. We expect to click on each button continuously, and the component will be rendered only once, so we started the experiment:
effect:
Sure enough, we changed number
setNumber
. Each time the button is clicked continuously, the component will be updated twice. According to our normal understanding, each time the number
, it will only be rendered once, but why is it executed twice?
You may get into trouble at the beginning and don't know how to solve the case, but we hooks
in the 060bc414f209ee principle that each function component uses the fiber
object of the hooks
information. So we can only find clues fiber
Follow the vine
So how do you find fiber object functions corresponding components of it, along which the parent function components Home
start, because we can from class components Home
find the corresponding fiber object, and then according to child
find components function pointer Index
corresponding fiber
. Just do it, we will transform the above code into the following:
const Index = ({ consoleFiber }) => {
const [ number , setNumber ] = useState(0)
useEffect(()=>{
console.log(number)
consoleFiber() // 每次fiber更新后,打印 fiber 检测 fiber变化
})
return <div className="page" >
<div className="content" >
<span>{ number }</span><br/>
<button onClick={ () => setNumber(1) } >将number设置成1</button><br/>
</div>
</div>
}
export default class Home extends React.Component{
consoleChildrenFiber(){
console.log(this._reactInternalFiber.child) /* 用来打印函数组件 Index 对应的fiber */
}
render(){
return <Index consoleFiber={ this.consoleChildrenFiber.bind(this) } />
}
}
We focus on these attributes on the fiber, which is very helpful to solve the case
Index fiber
onmemoizedState
property,react hooks
principle of the article mentioned, the function components withmemoizedState
save allhooks
information.Index fiber
onalternate
propertyIndex fiber
attribute onalternate
memoizedState
. Isn't it very confusing? I will reveal what it is soon.Index
on assemblyuseState
innumber
.
First, let's talk about what the alternate
pointer refers to?
Speaking of alternate
we should start with the fiber
architecture design. Each React
element node uses two fiber trees to save the state, one tree to save the current state, and one tree to save the last state. Two fiber
trees point to each other alternate
It is the well-known double-buffered .
Initial printing
renderings:
initialization of 160bc414f20b7a is completed for the first render, let’s take a look at these states on the fiber tree
The first print result is as follows,
fiber
onmemoizedState
inbaseState = 0
i.e. initializationuseState
value.fiber
onalternate
isnull
.Index
on assemblynumber
is 0.
Initialization process: First, for the first initialization of the component, it will be reconciled and rendered to form a fiber tree (we referred to as tree A ). A tree of alternate
property null
.
Click setNumber(1) for the first time
We clicked for the first time and found that the component was rendered, and then we printed the result as follows:
- On the tree A
memoizedState
in **baseState = 0
. alternate
on tree A points to anotherfiber
(we call it tree B here).Index
on assemblynumber
1.
Next we print memoizedState
It turns out that the tree B memoizedState
on baseState = 1
.
It is concluded that the updated state is on tree B, and the baseState on tree A is still the previous 0.
Let's make a bold guess about the update process: when the rendering is updated for the first time, since alternate
does not exist in tree A, we directly copy a copy of tree A as workInProgress
(we call it tree B here) and all the updates are in In the current tree B, so the baseState will be updated to 1, and then use the current tree B for rendering. After the end, tree A and tree B point to each other alternate
Tree B is used as the current
tree for the next operation.
Click setNumber(1) for the second time
second time, the components are also rendered, and then we print the fiber object, the effect is as follows:
memoizedState
inbaseState
on the fiber object is updated to 1.
Then we print alternate
in baseState
is also updated to 1.
After the second click, both tree A and tree B are updated to the latest baseState = 1
First, we analyze the process: when we clicked for the second time, the state in tree A was not updated to the latest state, and the component was updated again. Next, the tree A pointed to alternate
current tree (tree B) will workInProgress
. At this time, the baseState on tree A is finally updated to 1, which explains why the above two baseStates are both equal to 1. Next, the component rendering is complete. Tree A serves as the new current tree.
In our second printing, print out the actual tree after alternating B, A and tree B tree so alternately as the date for rendering workInProgress
on trees and a buffer state for the next rendering current
tree.
The third click (more than three)
Then the third click component is not rendered, it is easy to explain, the third click the last time baseState = 1
and setNumber(1)
in tree B are equal, and the return logic is directly gone.
Unravel the mystery (what have we learned)
- Double buffering tree: React uses
workInProgress
tree (tree built in memory) andcurrent
(rendering tree) to implement the update logic.workInProgress
printed by our console.log are all fiber trees with 060bc414f20e95 in memory. The double cache is built in memory. In the next rendering, the cache tree is directly used as the next rendering tree, and the previous rendering tree is used as the cache tree. This can prevent the loss of update status with only one tree. Speed up the replacement and updatedom
- Update mechanism: In an update, the
alternate
current tree is first obtained as the currentworkInProgress
. After rendering, theworkInProgress
tree becomes acurrent
tree. We use the above tree A and tree B and the saved baseState model to explain the update mechanism more vividly.
We use a flowchart to describe the entire process.
This case has been solved. Through this easily overlooked case, we have learned the double buffering and update mechanism.
Case six: useEffect to modify the DOM element causes a weird flash
curious coincidence
Xiao Ming (pseudonym) encountered a strange Dom flash phenomenon when dynamically mounting components. Let's take a look at the phenomenon first.
flash phenomenon:
code:
function Index({ offset }){
const card = React.useRef(null)
React.useEffect(()=>{
card.current.style.left = offset
},[])
return <div className='box' >
<div className='card custom' ref={card} >《 React进阶实践指南 》</div>
</div>
}
export default function Home({ offset = '300px' }){
const [ isRender , setRender ] = React.useState(false)
return <div>
{ isRender && <Index offset={offset} /> }
<button onClick={ ()=>setRender(true) } > 挂载</button>
</div>
}
isRender
dynamically loadIndex
in the parent component, and click the button to controlIndex
rendering.- The dynamic offset
Index
offset
. And by the controllinguseRef
acquired nativedom
changing the offset directly, so that the draw block slide. However, the flash phenomenon shown in the above picture has appeared, which is very unfriendly, so why does it cause this problem?
understand deeper
It is preliminary judged that the problem of this flash should be useEffect
. Why do you say that, because the componentDidMount
, but this phenomenon does not occur. So why useEffect
will cause this situation, we can only follow the vine to find the execution timing of useEffect
callback
useEffect
,useLayoutEffect
, componentDidMount
executed at the commit
stage. We know that React has a effectList
store different effect
. Because React
has different execution logic and timing effect
Let's take a look at what type of effect
useEffect
is defined.
react-reconciler/src/ReactFiberHooks.js
function mountEffect(create, deps){
return mountEffectImpl(
UpdateEffect | PassiveEffect, // PassiveEffect
HookPassive,
create,
deps,
);
}
The information of this function is as follows:
useEffect
is assignedPassiveEffect
typeeffect
.- The function for Xiaoming to change the location of the native dom is
create
.
So when is the create
function executed, and how does React handle PassiveEffect
? This is the key to solving the case. Write it down and let's take a look at how React handles PassiveEffect
.
react-reconciler/src/ReactFiberCommitWork.js
function commitBeforeMutationEffects() {
while (nextEffect !== null) {
if ((effectTag & Passive) !== NoEffect) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
/* 异步调度 - PassiveEffect */
scheduleCallback(NormalPriority, () => {
flushPassiveEffects();
return null;
});
}
}
nextEffect = nextEffect.nextEffect;
}
}
In commitBeforeMutationEffects
function, asynchronously scheduling flushPassiveEffects
method, flushPassiveEffects
method, for React hooks performs commitPassiveHookEffects
, then executes commitHookEffectListMount
.
function commitHookEffectListMount(){
if (lastEffect !== null) {
effect.destroy = create(); /* 执行useEffect中饿 */
}
}
In commitHookEffectListMount
, the create
function will be called. The position we add to the dom
element will take effect.
So the question is, what does asynchronous scheduling do? React's asynchronous scheduling, in order to prevent the execution of some tasks from delaying the browser's drawing, and causing the phenomenon of jams, React uses asynchronous scheduling to process some tasks with low priority, which means that the browser can only perform these tasks in idle time. Asynchronous tasks. Asynchronous tasks are executed on different platforms and different browsers in different ways. Let setTimeout
consider the effect as 060bc414f21190.
Sunny after rain
Through the above we find useEffect
first argument create
, the way the use of asynchronous call, then flashed on a good understanding, at the click of a button assembly for the first time the rendering process, the first execution of the function components render
, then commit
replace real dom Node, and then the browser is drawn. At this point, the browser has drawn once, and then the browser has free time to perform asynchronous tasks, so it executes create
, and modifies the position information of the element. Because the element has been drawn last time, a position is modified at this time, so I feel flashed As a result, the case has been solved. ,
So how do we solve the phenomenon of flashing, that is, React.useLayoutEffect
, useLayoutEffect
of create
are executed synchronously, so the browser draws once and directly updates the latest position.
React.useLayoutEffect(()=>{
card.current.style.left = offset
},[])
to sum up
What have we learned in this section?
React
from the perspective of solving the case from the perspective of principle. Through these phenomena, we have learned some internal things of React. I summarized the above case.
- Case one-understanding of some component rendering and component error timing statements
- Case Two-Supplement to the actual event pool concept.
- Case three-is the thinking and solution of
React
- Case four - to pay attention to
memo
/PureComponent
binding events, and how to handlePureComponent
logic,shallowEqual
principles. - Case 5-Actually, it is an
fiber
double cache tree of 060bc414f212bd. - Case 6-is an explanation of the timing of execution
useEffect create
Finally, give someone a rose and leave a lingering fragrance in your hand. Friends who think you have gained can give me likes, follow , and update the front-end super hard core articles one after another. Welcome to follow the author's public front-end Sharing , share hardcore articles for the first time
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。