React is prefered way to build large, responsive web applications in JavaScript.
ui = render(data) --> One-way data flow
Clearly, the key is a rapid response.
We know that Javascript excuation and DOM rendering are mutatually exclusive, Javascript execution blocks dom rendering, and DOM rendering similarly blocks Javascript execution. So why are browsers designed this way?
Becasue it is so simple! Javascript was initial just a collection of scripts for handing form validation, with very simple design considerations. Imagine if Javascrip execution and DOM rendering were to occur parallel, what would the DOM state be when Javascript manipulates it multiple times? Which operation should be responsed first? Solving this problem would require introducing thread locks, and at a deeper level, transaction mechanisms would also be needed, marking the whole process quite complex.
What factors limit rapid response?
Why do browsers drop frames during rendering?
Browsers render based on vertial synchronization signals, which has a period of16.67ms. If JS execution or rendering too long, You will miss a vertical synchronization signal and having to wait for next one, , It exceeds the minimum frame rate that human eyes can distinguish, and is identified as a drop frame.
How does React solve the above problems?
javascript// we can use `ReactDOM.unstable_createRoot` to open `Concurrent Mode`
// ReactDOM.render(<App/>, rootEl);
ReactDOM.unstable_createRoot(rootEl).render(<App/>);
In summary: The key to solve CPU bottlenecks is time slicing, and the key to time slicing is transforming synchronous updates into interruptible asynchronous updates.
Network latency is something developers cannot control, give that network latency is an objective reality, how can we reduce the user's feeling of it.
React's answer is, Intergrating the results of human-computer interaction research into real-world UIs,
Therefore react implemented function of Suspense and a suit of hooks useDeferredValue
The React 15 architecture can be divided into two layers:
We know that React can trigger updates using APIs such as this.setState、this.forceUpdate、ReactDOM.render.
Whenever an update occurs, the Reconciler performs the following tasks:
render method of a function component or a class component to convert the returned JSX into Virtual DOMVDOMVDOM elements that changed in this update.Renderer to render the changed VDOM onto the page.Since React supports cross-platform compatibility, different platforms have different Renderers
ReactDOM web渲染ReactNative 渲染APP原生组件ReactTest 渲染出纯JS对象,仅用于测试ReactArt 渲染到Canvas/SVG或VML(IE8)Disadvantages of React 15 architecture
React 15 uses recursive updates, and once the recursive starts, it cannot be interrupted, When DOM is very complex, the interaction will become lag.The React team has proposed a new solution, using interruptible asynchronous updates instead of synchronous updates.
The architure of React 16 is as follows
Scheduler(调度器)—— High-priority tasks are prioritized and enter the Reconciler
Reconciler(协调器)—— The component responsible for identify changes (render phase)
effectList linked list.Renderer(渲染器)—— responsible for rendering the changing components onto the page. (commit phase)
effectList linked list to reflect side effects in real DOM.As can be seen, React 16 added scheduler.
The key to solving the lag issue is to use interruptible asynchronous updates, So when can it be interrupted?
A mechanism is needed to determine if the browser has available time; in fact, some browsers already have this API
requestIdleCallback, However it suffers from compatibility and instability issues, and it does not support scheduling priorities
Therefore, React decided to implement its own schedule mechanism, which is the Scheduler.
The reconciler in React 15 uses recursion to process the VDOM, Recursive doesn't support interrupted updates, the DOM will be fully renderd during the update.
How to solve the problem of incomplete DOM updates?
Therefore, in React 16 the reconciler and renderer no longer work alternately, After the Scheduler hands over the task to Reconciler, Reconciler will mark changes to the VDOM (added/deleted/modified). The entire Scheduler and Reconciler process runs in memory. Only after all components have complete the Reconciler work will be handed over to the Renderer.

The steps in red box can be interrupted at any time.
Since the work within the red box is performed in memory and does not update the DOM on the page, the user will not see an incompletely updated DOM even if the process is repeatedly interrupted.
Fiberis a computer science term, translated into Chinese as纤程, It, along with threads and coroutines, is a part of the program's execution process. We understandsfibersandcoroutinesas manifestations oralgebraic effectconcept in javascript. In my opinion:React Fiberwas designed to implement thealgebraic effect. It is the unit for organizing the execution of fiber code.
algebraic effect.async/await, but async is contagious (The function calling it must also be async)try...handle and perform、resumejavascriptfunction getPrice(id) {
const price = perform id;
return price;
}
function getTotalPirce(id1, id2) {
const p1 = getPrice(id1);
const p2 = getPrice(id2);
return p1 + p2;
}
try {
getTotalPrice('001', '002');
} handle (productId) {
fetch(`xxx.com?id=${productId}`).then((res)=>{
resume with res.price
})
}
/*
How to understand the code above?
- the perform and handle are virtual syntax
- When the code executes to the `perform` method, the execution of current function is paused and captured by `handle` method.
- handle函数体会拿到productId参数获取数据之后resume价格
- resume会回到之前的perform暂停的地方并返回price
- 这就完全把副作用分离到了getTotalPrice和getPrice之外。
The key process here is: perform pauses the function's execution. handle acquires execution control, and the resume give up execution control.
*/
// 上述代码是抽象的,实际代码如下
function usePrice(id){
useEffect(() => {
fetch(`xxx.com?id=${productId}`).then((res) => res.price);
}, [])
}
function TotalPrice({id1, id2}) {
const price1 = usePrice(id1);
const price2 = usePrice(id2);
return <TotalPrice props={...}>
}
Q: Why not use Generator?
If we only consider the interruption and continuation of a single task, the Generator can effectively implement asynchronous interruptible updates.
When high-priority requests are interrupted, a global variable needs to be introduced to store the intermediate state of current generator. Additionally, creating a new high-priority generator introduces new complexity.
How can we understand how can fiber architecture pratices (simulates or implements) algebraic effect?
- Strictly speaking, React not support algebraic effects. You can use
try...handleto simulatetry...catch, and placethrow errorin a microtask. However, interruption and recovery can't be simulated.- But, React has
fiber, After the update of this fiber is completed, the execution is handed back to the browser, allowing the browser to decide how to schedule it. (Interruption and resumption can be understood as continuing to execute JS after rendering a frame; the fiber will save the itermediate state. )Suspense is also an extension of this concept.
- throw a Promise, 拿到数据通过Promise回调结果,外层组件收到回调结果触发setData, 给人的感觉是实现了代数效果
Fiber?Definition: React implements an internal state update mechanism. It supports different task priorities, and can reuse the previous intermediate state after resumption.
Functions:
reconciler in the React 15 architecture executes recursively and is called stack reconciler. The Reconciler in React 16 architecture is implemented based on Fiber and is called Fiber Reconciler.Fiber node corresponds to React elmement, storing the state of that component(The function needs to be deleted/ inserted into page/updated...);Fiber node stores the changes made to the component during this update, as well as the tasks to be performed(add, delete, update).Q: How does React Fiber update the DOM?
Use double caching (the technique of building in memory and performing replacement directly is called double caching).
In React, there can be a maximum of two fiber trees at the same time. The one display on screen is current fiber tree, and the fiber tree being built in memory is called workInProgress Fiber true. They are connected via a alternate attribute.
fiberRootNode The root node of the entire application.rootFiber <APP/> The root node of the component.All fiber nodes have the alternate attribute, any update to any component will modify the alternate pointer.
JSX and Fiber
JSX and Fiber nodes the same thing?React Component and React Element the same thing? What is their relationship of JSX?React.createElement method translates JSX into React elements. This is the reason why we import React from 'react';
JSX is a data structure that describes the current content of the current component. It does not contain the relevant information (schedule, reconciler, render) required by the components. (The component's priority during updates, the component's state, and the tags attached to the component for the Render).Fiber is more of an update mechanism.Reconciler generates the corresponding Fiber nodes for the component based on component context described in the JSX.update process, the Reconciler compared the data stored in JSX and Fiber nodes for the component, and marks the Fiber nodes based on comparison results.The execution of reconciler corresponds to the render phase, which mainly generates the new workInProgressFiberTree
Although fiber reconciler is refactored from stack reconciler, both implement interruptible asynchronous recursion through traversal.
beginWork Enter the current Fiber node, create child Fiber node.completeWork Different processing logic is invoked for different fiber.effectTag tagsmounting: except for fiberRootNode, current === null. Different types of child Fiber will be created based on different fiber.tag values.updating: If current exist, the current node can be reused under certain conditions. This allows current.child to be cloned as workInProgress.child, without needing to create a new workInProgress.child;Summary:beginWork is to create Fiber node

effectTag
fiber.effectTagDifferent processing logic is generated based on different fiber.tag
update, we also need to consider workInProgress.stateNode != null ?(i.e., whether the fiber node has corresponding DOM node)updating, The Fiber node already has a corresponding DOM node, so there is no need to generate a DOM node. The main task is to process props, such as:
onClick、onChange etc. callback function registration.style and propDANGEROUSLY_SET_INNER_HTML propchildren propFiber nodes.DOM nodes into the newly generated DOM node.updateHostComponent in the update logic.Summary: completeWork is processing the fiber nodes according to effectTag.

effectList
Q:As the basis of DOM operation, the completeWork phase needs to find all Fiber nodes' effectTag and execute the corresponding operations for each effectTag in sequence. Does this mean we need to traverse the fiber tree again in the commit phase to find effectTag !== null Fiber nodes?
A: completeWork在上层函数completeUnitOfWork上维护了一个单向链表
effectList中第一个Fiber节点保存在fiber.firstEffect,最后一个元素保存在fiber.lastEffect。
类似appendAllChildren,在“归”阶段,所有effectTag的Fiber节点都会被追加在effectList中,最终形成一条以rootFiber.firstEffect为起点的单向链表。

commitRoot(root);
rootFiber.firstEffect stores a singly linked list effectList containing Fiber nodes that need to have their side effects executed.updateQueue of these Fiber nodes stores the changed props.commit phase.componentDidXXX) and hooks(such as useEffect) need to be executed during the commit phase.commit phase(i.e. the Renderer workflow) consists of three parts:before mutation phase(before performing dom operations)
autoFocus and blur.getSnapshotBeforeUpdateuseEffectmutation phase(performing dom operations)
ContentReset and effectTag.refeffectTag, the effectTag includes Placement , Update , Deletion and Hydrating.layout(after performing DOM operations)
useEffect related processingQ: Why is the UNSAFE_ prefix added before componentWillXXX hooks starting from React 16?
In
Reactupdates, an update object is created each time an update is initiated. MutipleUpdateObjects for the same component are stored in anupdateQueuequeue. Suppose a componentupdateQueuehas fourupdatecalls, where the number represent priority.
javascriptbaseState = '';
A1 - B2 - C1 - D2
// 为了保证更新的连贯性,第一个被跳过的update(B)和后面的update会作为第二次渲染的baseUpdate
// 为BCD
// 首次渲染后
baseState: ''
Updates: [A1, C1]
Result state: 'AC'
// 第二次渲染,B在第一次渲染时被跳过,
// 所以在他之后的C造成的渲染结果不会体现在第二次渲染的baseState中。
// 所以baseState为A而不是上次渲染的Result state AC。
// 这也是为了保证更新的连贯性
baseState: 'A' // 为了保证一致性,C不在
Updates: [B2, C1, D2]
Result state: 'ABCD'
// Updates里出现了两次C
Q:Why asynchronous scheduling?
Unlike
componentDidMountandcomponentDidUpdate, the function passed touseEffectis called delayed after the browser has completed layout and rendering. This makes it suitable for many common side effect scenarios, such as setting up subscription and event handling, so operations that block the browser from updating the screen should not be performed in the function.
Prevent browser rendering from being blocked during synchronous execution.
Q:What's the operation with the highest time complexity in rendering the DOM?
getHostSibling(Get sibling DOM nodes)
When multiple operations are preformed sequentially under the same parent Fiber node, the complexity of the getHostSibling algorithm is exponential.
This is because Fiber nodes include more than HostComponent, so the fiber tree and the rendered DOM tree nodes are not in a one to one correspondence. finding a DOM node from a Fiber node may require traversing multiple levels.
javascriptfunction Item() {
return <li><li>;
}
function App() {
return (
<div>
<Item/>
</div>
)
}
ReactDOM.render(<App/>, document.getElementById('root'));
// Fiber树
child child child child
rootFiber -----> App -----> div -----> Item -----> li
// DOM树
#root ---> div ---> li
// 在div的子节点Item前加一个p
function App() {
return (
<div>
<p></p>
<Item/>
</div>
)
}
// Fiber树
child child child
rootFiber -----> App -----> div -----> p
| sibling child
| -------> Item -----> li
// DOM树
#root ---> div ---> p
|
---> li
// 此时dom中p的兄弟节点是li
// fiber中fiberP的兄弟节点是fiberItem, fiberItem的子节点才是li
Q:double caching switching execution time
After the mutation phase ends and before the layout phase begins.
componentWillUnmount executes during the mutation phase. At this time, current Fiber tree still points to the previously updated Fiber tree, and the DOM retrieved within the lifecycle hooks is still the one from before the update.componentDidMount and componentDidUpdate will perform during layout phase. At this point , the current Fiber tree points to the updated fiber tree, and the dom retrieved within the lifecycle hooks is the updated fiber tree.During render phase, for the components that are being updated, the current component is compared with Fiber nodes of last render(also known as diff algorithm), and the comparison results is used to generate a new fiber node.
willMount, willUpdate, DidUpdate)Combing the render and commit phases, a DOM node can have a maximum of 4 associated nodes:
current Fiber. If the DOM node is already in the page, current Fiber represents the Fiber node corresponding to the DOM node;workInProgress Fiber. If the DOM node will be rendered to the page in this update, workInProgress Fiber represents the Fiber node corresponding to the DOM node;diff algorithm: Compare 1 and 4 to generate 2.
The diff operation itself incurs performance overhead. The React documentation states that even in state-of-the-art algorithms, the complexity of a complete comparison of two trees is . where is the number of elements in the tree; if this algorithm were used in React, the computation required to display 1000 elements would be in the billions. This overhead is simply too high;
To reduce algorithm complexity. React's diff has tree predefined limitations:
Based on the number of nodes at the same level, the Diff is divided into two categories:
Single-node diff
child !== null and keys are the same but the types are different, executing deleteRemainingChildren will mark the child and its sibling fibers as deleted;child !== null and keys are different, only child tag is deleted;The approach provided by React team: 2 rounds of iteration.
First round of traversal --- Find reusable nodes (identified by their headers), lastIndex represents the index of last reusable node.
Second round of travelsal.
Let's first analyze the four possible outcomes after the first round of traversal.
How to handle updated notes? -- Mark whether the node has moved
txt// before abcd // after acdb ===The first round of traversal begins=== a(after)vs a(before) The key remains the same and can be reused. At this point, the oldFiber corresponding to 'a'(the previous `a`) and has an index of 0 in the previous array (abcd), so `lastPlacedIndex = 0`; continue with the first round of traversal... c(after)vs b(before) the key changes, can't reuse, therefore, exit the first iteration, At this point `lastPlacedIndex === 0;` ===The first round of traversal ends=== ===The second round of traversal starts=== newChildren === cdb, Not used up, No need to delete old nodes. oldFiber === bcd, Not used up, No need to insert new nodes. Saving the remaining oldFiber(bcd) as a map // current oldFiber: bcd // current newChildren: cdb continue iterating through the remaining newChildren `key === c` exists in oldFiber const oldIndex = c(before).index; At this time `oldIndex === 2;` // 之前节点为 abcd,所以c.index === 2 Compare `oldIndex` with `lastPlacedIndex`; if `oldIndex >= lastPlacedIndex` This means the node is reusable and does not need to be moved. setting `lastPlacedIndex = oldIndex;` if `oldIndex < lastplacedIndex` 该可复用节点之前插入的位置索引小于这次更新需要插入的位置索引,This means the node needs to be moved to the right. In the example, `oldIndex 2 > lastPlacedIndex 0`, so lastPlacedIndex = 2; The position of node c remains unchanged Continue iterating through the remaining `newChildren` // current oldFiber :bd // current newChildren :db `key === d` exists in oldFiber const oldIndex = d(before).index; oldIndex 3 > lastPlacedIndex 2 // 之前节点为 abcd,所以d.index === 3 so lastPlacedIndex = 3; The position of node d remains unchanged Continue iterating through the remaining `newChildren` // current oldFiber :b // current newChildren :b `key === b` exists in oldFiber const oldIndex = b(before).index; oldIndex 1 < lastPlacedIndex 3 // 之前节点为 abcd,所以b.index === 1 Then node b needs to move to the right. ===The second round of traversal ends=== finally, nodes a, c and d did not move, while node b was marked as moved.
Let's revise the case and analyze it again
javascript// before
abcd
// after
dabc
===The first round of traversal begins===
d(after)vs a(before)
key changed, can't be used, Break out of traversal
===The first round of traversal ends===
===The second round of traversal begins===
newChildren === dabc, not used up, no need to delete old nodes.
oldFiber === abcd, not used up, no need to insert new nodes.
Save the remaining oldFiber(abcd) as a map
continue iterating through the remaining newChildren
// current oldFiber : abcd
// current newChildren: dabc
`key === d` exists in oldFiber
const oldIndex = d(before).index;
At this point oldIndex === 3; // 之前节点为 abcd,所以d.index === 3
compare oldIndex with lastPlacedIndex;
oldIndex 3 > lastPlacedIndex 0
so `lastPlacedIndex = 3;`
The position of node d remains unchanged
continue iterating through the remaining newChildren
// current oldFiber : abc
// current newChildren : abc
`key === a` exists in oldFiber
const oldIndex = a(before).index; // 之前节点为 abcd,所以a.index === 0
At this point `oldIndex === 0;`
compare oldIndex and lastPlacedIndex;
oldIndex 0 < lastPlacedIndex 3
Then node a needs to move to the right.
Continue iterating through the remaining newChildren
// current oldFiber : bc
// current newChildren : bc
`key === b` exists in oldFiber
const oldIndex = b(before).index; // 之前节点为 abcd,所以b.index === 1
At this point oldIndex === 1;
compare oldIndex and lastPlacedIndex;
oldIndex 1 < lastPlacedIndex 3
The node b needs to move to right
Continue iterating through the remaining newChildren
// current oldFiber : c
// current newChildren : c
key === c exists in oldFiber
const oldIndex = c(before).index; // 之前节点为 abcd,所以c.index === 2
At this point `oldIndex === 2;`
compre oldIndex and lastPlacedIndex;
oldIndex 2 < lastPlacedIndex 3
Then node c needs to be moved to the right.
===The second round of traversal ends===
Summary: Set up the reference point to minimize the number of operations required to move nodes from the back to the front.
typescript/**
* react diff
* 1. abcd -> acdb
* 2. abcd -> dabc
*/
export function diffChildren(newStr: string, oldStr: string): string[] {
// 第一轮循环 找出头部无变更的元素
let lastPlaceIndex = 0;
const result: string[] = [];
for (let i = 0; i < newStr.length; i++) {
if (newStr[i] && oldStr[i] && newStr[i] === oldStr[i]) {
lastPlaceIndex = i+1;
} else {
break;
}
}
if (
lastPlaceIndex === newStr.length &&
lastPlaceIndex === oldStr.length
) {
result.push("第一轮遍历结束,全部节点都可以复用");
return result;
}
if (lastPlaceIndex === oldStr.length) {
result.push("第一轮遍历结束, 新children有剩余");
result.push(`将剩余的新节点${newStr.slice(lastPlaceIndex)}插入尾部`);
return result;
}
if (lastPlaceIndex === newStr.length) {
result.push("第一轮遍历结束, 旧children有剩余");
result.push(`将剩余的新节点${oldStr.slice(lastPlaceIndex)}删除`);
return result;
}
result.push(`第一轮遍历结束新旧children都有剩余的情况`);
// 第二轮循环
const restOfOld = [...oldStr.slice(lastPlaceIndex)];
const [map, deletionsMap] = restOfOld.reduce(([map1, map2], item, index) => {
map1[item] = index + lastPlaceIndex;
const map2Index = index + lastPlaceIndex + '';
map2[map2Index] = true;
return [map1, map2];
}, [{}, {}] as [{[key: string]: number}, {[key: string]: boolean}]);
const restOfNew = [...newStr.slice(lastPlaceIndex)];
for (let j = 0; j < restOfNew.length; j++) {
const oldIndex = map[restOfNew[j]];
if(oldIndex !== undefined) {
deletionsMap[oldIndex] = false;
}
if(oldIndex === undefined) {
result.push(`将新元素${restOfNew[j]}插入到尾部`);
lastPlaceIndex = Math.max(oldStr.length, lastPlaceIndex) + 1;
} else if (oldIndex > lastPlaceIndex) {
lastPlaceIndex = oldIndex;
} else if (oldIndex < lastPlaceIndex) {
result.push(`将${restOfNew[j]}移动到尾部`);
}
}
Object.keys(deletionsMap)
.filter(index => deletionsMap[index])
.forEach(index => {
console.log(`删除${oldStr[index]}`)
result.push(`删除${oldStr[index]}`);
})
return result;
}
// diff.test.ts
import { diffChildren } from "./diff";
import { describe, it, expect } from "vitest";
describe("diffChildren", () => {
it("第一轮遍历结束,全部节点都可以复用", async () => {
const result = diffChildren('abcd', 'abcd');
console.log(result);
expect(result).toEqual(['第一轮遍历结束,全部节点都可以复用'])
});
it("第一轮遍历结束,新children有剩余", () => {
const result = diffChildren('abcd', 'abc');
console.log(result);
expect(result).toEqual([
'第一轮遍历结束, 新children有剩余',
'将剩余的新节点d插入尾部'
])
})
it("第一轮遍历结束,旧children有剩余", () => {
const result = diffChildren('abc', 'abcd');
console.log(result);
expect(result).toEqual([
'第一轮遍历结束, 旧children有剩余',
'将剩余的新节点d删除'
])
})
it("元素移动:abcd --> acdb", () => {
const result = diffChildren('acdb', 'abcd');
console.log(result);
expect(result).toEqual([
'第一轮遍历结束新旧children都有剩余的情况',
'将b移动到尾部'
])
})
it("元素移动:abcd --> dabc", () => {
const result = diffChildren('dabc', 'abcd');
console.log(result);
expect(result).toEqual([
'第一轮遍历结束新旧children都有剩余的情况',
"将a移动到尾部",
'将b移动到尾部',
'将c移动到尾部',
])
});
it("有元素移动和新增元素:abcd --> dabce", () => {
const result = diffChildren('dabce', 'abcd');
console.log(result);
expect(result).toEqual([
'第一轮遍历结束新旧children都有剩余的情况',
"将a移动到尾部",
'将b移动到尾部',
'将c移动到尾部',
"将新元素e插入到尾部",
]);
});
it("有元素移动和新增元素:abcd --> daebc", () => {
const result = diffChildren('daebc', 'abcd');
console.log(result);
expect(result).toEqual([
'第一轮遍历结束新旧children都有剩余的情况',
"将a移动到尾部",
"将新元素e插入到尾部",
'将b移动到尾部',
'将c移动到尾部',
]);
})
it("有元素移动和新增元素:abcd --> dac", () => {
const result = diffChildren('dac', 'abcd');
console.log(result);
expect(result).toEqual([
'第一轮遍历结束新旧children都有剩余的情况',
"将a移动到尾部",
'将c移动到尾部',
'删除b',
]);
});
});
In React, the operations that trigger state updates includes:
Q: How can we integrate the same state management mechanism across different application scenarios?
Each time the state is updated, an object related to the updated state is created and stored, and is called Update, in the beginwork of render, a new state is obtained based on Update

Update approach, it can be compared to code version control.
Update and contact with Fiber:
fiber.updateQueueupdateQueues at the same time:current fiber/workInProgress fiberupdateQueue
typescriptconst queue: UpdateQueue<State> = {
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
},
effects: null,
};
Update calculates the updated state based on this state, You can think of baseState as the master branch in a mental model;Update data prior to this update. It exists as a linked list, with the firstBashUpdate of the head and lastBaseUpdate at the tail. The reason why an Update exists within a fiber node before the Update is generated, is because some Updates have low priority and were skipped (when the state was calculated by the Update during the last render phase). You can draw an analogy between baseUpdate and commit (node D) upon which git rebase is based in a mental model.Update will be stored in shared.pending, forming a singly linked circular list. 当由Update计算state时这个环会被剪开并连接在lastBaseUpdate后面,可以将shared.pending类比心智模型中本次需要提交的commit(节点ABC)。update.callback !== null for Update;Q: The render phase may be interrupted. How can we ensure the Update data of updateQueue is not lose?
During the render phase在, the shared.pending loop is cut and connected after updateQueue.lastBaseUpdate.
In fact, shared.pending is connected after workInProgress updateQueue.lastBaseUpdate and current updateQueue.lastBaseUpdate.
When the render phase is interrupted and then restarted, a workInProgress updateQueue will be cloned based on the current updateQueue. Since current updateQueue.lastBaseUpdate has already saved the previous Update, it will not be lost. Because the previous Update is store in workInProgress updateQueue.lastBaseUpdate, the update will not be lost even after the the workInProgress Fiber tree become the current Fiber tree.
Architecture operation stategy--lane mode
Based on the current architecture, when an update is interrupted during operation and then resumes after a period of time, this is called an "asynchronous interruptible update"
When an update is interrupted during its execution, a new update will be started instead. We can say that the later update interrupt the previous update;
This is concept of priority: The later update has higher priority and will interrupted the previous update that is in progress.
How can multi priorities interrupt each other? Can priority be increased and decreased? What priority should this update be given?
This requires a model to control the relationships and behaviors between different priorities, which is lane mode.
Time slicing principle
The essence of time slicing is simulation implementation requestIdleCallback.
除去“浏览器重排/重绘”,下图是浏览器一帧中可以用于执行JS的时机。
一个task(宏任务) -- 队列中全部job(微任务) -- requestAnimationFrame -- 浏览器重排/重绘 -- requestIdleCallback
Scheduler的时间切片功能是通过task(宏任务)实现的。
所以Scheduler将需要被执行的回调函数作为MessageChannel的回调执行。如果当前宿主环境不支持MessageChannel,则使用setTimeout 在React的render阶段,开启Concurrent Mode时,每次遍历前,都会通过Scheduler提供的shouldYield方法判断是否需要中断遍历,使浏览器有时间渲染:
为什么条件语句里面不能写hooks?
legacy模式 与 concurrent模式的差异
legacy模式 使用ReactDOM.render()创建的应用 并发模式concurrent 使用ReactDOM.createRoot().render()创建的应用
本文作者:郭敬文
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!