Web
应用程序JavaScript
的语法扩展,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式react/jsx-runtime
和 react/jsx-dev-runtime
中的函数只能由编译器转换使用。如果你需要在代码中手动创建元素,你可以继续使用 React.createElement
const babel = require("@babel/core");
const sourceCode = `
<h1>
hello<span style={{ color: "red" }}>world</span>
</h1>
`;
const result = babel.transform(sourceCode, {
plugins: [["@babel/plugin-transform-react-jsx", { runtime: "classic" }]],
});
console.log(result.code);
React.createElement(
"h1",
null,
"hello",
React.createElement(
"span",
{
style: {
color: "red",
},
},
"world"
)
);
const babel = require("@babel/core");
const sourceCode = `
<h1>
hello<span style={{ color: "red" }}>world</span>
</h1>
`;
const result = babel.transform(sourceCode, {
+ plugins: [["@babel/plugin-transform-react-jsx", { runtime: "automatic" }]],
});
console.log(result.code);
var { jsxDEV } = require("react/jsx-dev-runtime");
jsxDEV("h1", {
children: [
"hello",
jsxDEV("span", {
style: {
color: "red",
},
children: "world",
}),
],
});
React.createElement
函数所返回的就是一个虚拟 DOMconst UpdateState = 0;
function initializeUpdateQueue(fiber) {
const queue = {
shared: {
pending: null,
},
};
fiber.updateQueue = queue;
}
function createUpdate() {
const update = { tag: UpdateState };
return update;
}
function enqueueUpdate(fiber, update) {
const updateQueue = fiber.updateQueue;
const sharedQueue = updateQueue.shared;
const pending = sharedQueue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
updateQueue.shared.pending = update;
}
function getStateFromUpdate(update, prevState) {
switch (update.tag) {
case UpdateState: {
const { payload } = update;
const partialState = payload;
return Object.assign({}, prevState, partialState);
}
default:
return prevState;
}
}
function processUpdateQueue(workInProgress) {
const queue = workInProgress.updateQueue;
const pendingQueue = queue.shared.pending;
if (pendingQueue !== null) {
queue.shared.pending = null;
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = null;
let newState = workInProgress.memoizedState;
let update = firstPendingUpdate;
while (update) {
newState = getStateFromUpdate(update, newState);
update = update.next;
}
workInProgress.memoizedState = newState;
}
}
let fiber = { memoizedState: { id: 1 } };
initializeUpdateQueue(fiber);
let update1 = createUpdate();
update1.payload = { name: "zhufeng" };
enqueueUpdate(fiber, update1);
let update2 = createUpdate();
update2.payload = { age: 14 };
enqueueUpdate(fiber, update2);
processUpdateQueue(fiber);
console.log(fiber);
requestIdleCallback
使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应requestIdleCallback
里注册的任务<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<script>
function sleep(d) {
for (var t = Date.now(); Date.now() - t <= d; );
}
const works = [
() => {
console.log("第1个任务开始");
sleep(20); //sleep(20);
console.log("第1个任务结束");
},
() => {
console.log("第2个任务开始");
sleep(20); //sleep(20);
console.log("第2个任务结束");
},
() => {
console.log("第3个任务开始");
sleep(20); //sleep(20);
console.log("第3个任务结束");
},
];
requestIdleCallback(workLoop);
function workLoop(deadline) {
console.log("本帧剩余时间", parseInt(deadline.timeRemaining()));
while (deadline.timeRemaining() > 1 && works.length > 0) {
performUnitOfWork();
}
if (works.length > 0) {
console.log(`只剩下${parseInt(deadline.timeRemaining())}ms,时间片到了等待下次空闲时间的调度`);
requestIdleCallback(workLoop);
}
}
function performUnitOfWork() {
works.shift()();
}
</script>
</body>
</html>
Fiber
Depth First Search
function dfs(node) {
console.log(node.name);
node.children &&
node.children.forEach((child) => {
dfs(child);
});
}
let root = {
name: "A",
children: [
{
name: "B",
children: [{ name: "B1" }, { name: "B2" }],
},
{
name: "C",
children: [{ name: "C1" }, { name: "C2" }],
},
],
};
dfs(root);
k
的所有顶点,然后再去搜索距离为k+l
的其他顶点function bfs(node) {
const stack = [];
stack.push(node);
let current;
while ((current = stack.shift())) {
console.log(current.name);
current.children &&
current.children.forEach((child) => {
stack.push(child);
});
}
}
let root = {
name: "A",
children: [
{
name: "B",
children: [{ name: "B1" }, { name: "B2" }],
},
{
name: "C",
children: [{ name: "C1" }, { name: "C2" }],
},
],
};
bfs(root);
class Stack {
constructor() {
this.data = [];
this.top = 0;
}
push(node) {
this.data[this.top++] = node;
}
pop() {
return this.data[--this.top];
}
peek() {
return this.data[this.top - 1];
}
size() {
return this.top;
}
clear() {
this.top = 0;
}
}
const stack = new Stack();
stack.push("1");
stack.push("2");
stack.push("3");
console.log("stack.size()", stack.size());
console.log("stack.peek", stack.peek());
console.log("stack.pop()", stack.pop());
console.log("stack.peek()", stack.peek());
stack.push("4");
console.log("stack.peek", stack.peek());
stack.clear();
console.log("stack.size", stack.size());
stack.push("5");
console.log("stack.peek", stack.peek());
0b1000=2*2*2=Math.pow(2,3)=8
运算 | 使用 | 说明 |
---|---|---|
按位与(&) | x & y | 每一个比特位都为 1 时,结果为 1,否则为 0 |
按位或() | x y | 每一个比特位都为 0 时,结果为 0,否则为 1 |
//定义常量
const Placement = 0b001; // 0b001
const Update = 0b010; // 0b010
//定义操作
let flags = 0b000;
//增加操作
flags |= Placement;
flags |= Update;
console.log(flags.toString(2)); //0b11
//删除操作
flags = flags & ~Placement;
console.log(flags.toString(2)); //0b10
//判断包含
console.log((flags & Placement) === Placement);
console.log((flags & Update) === Update);
//判断不包含
console.log((flags & Placement) === 0);
console.log((flags & Update) === 0);
document->body->div->button
//w3c浏览器:event.target
//IE6、7、8: event.srcElement
let target = event.target || event.srcElement;
button->div->body->document
useCapture
参数是true
,则在捕获阶段绑定函数,反之false
,在冒泡阶段绑定函数element.addEventListener(event, function, useCapture)
cancelBubble
的属性为 truestopPropagation()
方法function stopPropagation(event) {
if (!event) {
window.event.cancelBubble = true;
}
if (event.stopPropagation) {
event.stopPropagation();
}
}
function preventDefault(event) {
if (!event) {
window.event.returnValue = false;
}
if (event.preventDefault) {
event.preventDefault();
}
}
子元素
的事件委托给父元素
,让父元素负责事件监听事件冒泡
来实现的<body>
<ul id="list" onclick="show(event)">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
<li>item n</li>
</ul>
<script>
function show(event) {
alert(event.target.innerHTML);
}
</script>
</body>
import * as React from "react";
import * as ReactDOM from "react-dom";
class App extends React.Component {
parentRef = React.createRef();
childRef = React.createRef();
componentDidMount() {
this.parentRef.current.addEventListener(
"click",
() => {
console.log("父元素原生捕获");
},
true
);
this.parentRef.current.addEventListener("click", () => {
console.log("父元素原生冒泡");
});
this.childRef.current.addEventListener(
"click",
() => {
console.log("子元素原生捕获");
},
true
);
this.childRef.current.addEventListener("click", () => {
console.log("子元素原生冒泡");
});
document.addEventListener(
"click",
() => {
console.log("document原生捕获");
},
true
);
document.addEventListener("click", () => {
console.log("document原生冒泡");
});
}
parentBubble = () => {
console.log("父元素React事件冒泡");
};
childBubble = () => {
console.log("子元素React事件冒泡");
};
parentCapture = () => {
console.log("父元素React事件捕获");
};
childCapture = () => {
console.log("子元素React事件捕获");
};
render() {
return (
<div ref={this.parentRef} onClick={this.parentBubble} onClickCapture={this.parentCapture}>
<p ref={this.childRef} onClick={this.childBubble} onClickCapture={this.childCapture}>
事件执行顺序
</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
/**
document原生捕获
父元素React事件捕获
子元素React事件捕获
父元素原生捕获
子元素原生捕获
子元素原生冒泡
父元素原生冒泡
子元素React事件冒泡
父元素React事件冒泡
document原生冒泡
*/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>event</title>
</head>
<body>
<div id="root">
<div id="parent">
<div id="child">点击</div>
</div>
</div>
<script>
let root = document.getElementById("root");
let parent = document.getElementById("parent");
let child = document.getElementById("child");
//listenToNativeEvent('click',false,root);
//listenToNativeEvent( 'click',true,root);
//root的捕获阶段的处理函数
root.addEventListener("click", (event) => dispatchEvent(event, true), true);
//root的冒泡阶段的处理函数
root.addEventListener("click", (event) => dispatchEvent(event, false), false);
function dispatchEvent(event, isCapture) {
//console.log(event,isCapture);
let paths = []; //事件的传播路径数组[child,parent,root,body,document]
let currentTarget = event.target; //事件源
while (currentTarget) {
paths.push(currentTarget);
currentTarget = currentTarget.parentNode;
}
if (isCapture) {
//如果当前是捕获阶段
for (let i = paths.length - 1; i >= 0; i--) {
//[document,body,root,parent.child]
let handler = paths[i].onClickCapture; //react捕获事件
handler && handler();
}
} else {
for (let i = 0; i < paths.length; i++) {
//[child,parent,root,body,document]
let handler = paths[i].onClick; //react冒泡事件
handler && handler();
}
}
}
root.addEventListener("click", (event) => console.log("根元素原生事件捕获"), true);
root.addEventListener("click", (event) => console.log("根元素原生事件冒泡"), false);
parent.addEventListener(
"click",
() => {
console.log("父元素原生事件捕获");
},
true
);
parent.addEventListener(
"click",
() => {
console.log("父元素原生事件冒泡");
},
false
);
child.addEventListener(
"click",
() => {
console.log("子元素原生事件捕获");
},
true
);
child.addEventListener(
"click",
() => {
console.log("子元素原生事件冒泡");
},
false
);
parent.onClick = () => {
console.log("React:父元素React事件冒泡");
};
parent.onClickCapture = () => {
console.log("React:父元素React事件捕获");
};
child.onClick = () => {
console.log("React:子元素React事件冒泡");
};
child.onClickCapture = () => {
console.log("React:子元素React事件捕获");
};
</script>
</body>
</html>
siftDown
函数向下调整堆siftUp
函数向上调整堆react\packages\scheduler\src\SchedulerMinHeap.js
export function push(heap, node) {
const index = heap.length;
heap.push(node);
siftUp(heap, node, index);
}
export function peek(heap) {
const first = heap[0];
return first === undefined ? null : first;
}
export function pop(heap) {
const first = heap[0];
if (first !== undefined) {
const last = heap.pop();
if (last !== first) {
heap[0] = last;
siftDown(heap, last, 0);
}
return first;
} else {
return null;
}
}
function siftUp(heap, node, i) {
let index = i;
while (true) {
const parentIndex = index - 1 >>> 1;
const parent = heap[parentIndex];
if (parent !== undefined && compare(parent, node) > 0) {
heap[parentIndex] = node;
heap[index] = parent;
index = parentIndex;
} else {
return;
}
}
}
function siftDown(heap, node, i) {
let index = i;
const length = heap.length;
while (index < length) {
const leftIndex = (index + 1) * 2 - 1;
const left = heap[leftIndex];
const rightIndex = leftIndex + 1;
const right = heap[rightIndex];
if (left !== undefined && compare(left, node) < 0) {
if (right !== undefined && compare(right, left) < 0) {
heap[index] = right;
heap[rightIndex] = node;
index = rightIndex;
} else {
heap[index] = left;
heap[leftIndex] = node;
index = leftIndex;
}
} else if (right !== undefined && compare(right, node) < 0) {
heap[index] = right;
heap[rightIndex] = node;
index = rightIndex;
} else {
return;
}
}
}
function compare(a, b) {
const diff = a.sortIndex - b.sortIndex;
return diff !== 0 ? diff : a.id - b.id;
}
const { push, pop, peek } = require('./SchedulerMinHeap');
let heap = [];
push(heap, { sortIndex: 1 });
push(heap, { sortIndex: 2 });
push(heap, { sortIndex: 3 });
console.log(peek(heap));
push(heap, { sortIndex: 4 });
push(heap, { sortIndex: 5 });
push(heap, { sortIndex: 6 });
push(heap, { sortIndex: 7 });
console.log(peek(heap));
pop(heap);
console.log(peek(heap));
requestIdleCallback
目前只有Chrome支持var channel = new MessageChannel();
//channel.port1
//channel.port2
var channel = new MessageChannel();
var port1 = channel.port1;
var port2 = channel.port2
port1.onmessage = function(event) {
console.log("port1收到来自port2的数据:" + event.data);
}
port2.onmessage = function(event) {
console.log("port2收到来自port1的数据:" + event.data);
}
port1.postMessage("发送给port2");
port2.postMessage("发送给port1");
+ 00000001 # +1
- 00000001 # -1
10000000
的意思是-0,这个没有意义,所有这个数字被用来表示-1280 0000001 # +1
1 0000001 # -1
0 0000001 # +1
1 1111110 # -1
0 0000001 # +1
1 1111111 # -1
0b00000011
3
~0b00000011 => 0b11111100
-4
(~0b00000011).toString();
'-4'
(~0b00000011).toString(2);
'-100'
求补码的真值
1 表示负号
剩下的 1111100 开始转换
1111100 减1
1111011 取反
0000100 4
/**
* 分离出所有比特位中最右边的1
* 分离出最高优先级的车道
* @param {*} lanes 车道
* @returns 车道
*/
function getHighestPriorityLane(lanes) {
return lanes & -lanes;
}
lanes=0b00001100=12
-lanes=-12
1
0001100
1110011
1110100
11110100
00001100
(0b00000010<<1).toString(2)
(-0b111>>1).toString(2) "-100"
-0b111 -7
100000111 原码
111111000 反码
111111001 补码
111111100
1
111111100
111111011
000000100
1000000100
-100
-4
(0b111>>>1).toString(2)
>>> "11"
(-0b111>>>1).toString(2)
>>> "1111111111111111111111111111100"
00000000000000000000000000000111
11111111111111111111111111111000
11111111111111111111111111111001
01111111111111111111111111111100
2147483644
//一共有16种优先级
//同步优先级
const SyncLanePriority = 15;
const SyncBatchedLanePriority = 14;
//离散事件优先级
const InputDiscreteHydrationLanePriority = 13;
const InputDiscreteLanePriority = 12;
//连续事件优先级
const InputContinuousHydrationLanePriority = 11;
const InputContinuousLanePriority = 10;
//默认优先级
const DefaultHydrationLanePriority = 9;
const DefaultLanePriority = 8;
//渐变优先级
const TransitionHydrationPriority = 7;
const TransitionPriority = 6;
const RetryLanePriority = 5;
const SelectiveHydrationLanePriority = 4;
//空闲优先级
const IdleHydrationLanePriority = 3;
const IdleLanePriority = 2;
//离屏优先级
const OffscreenLanePriority = 1;
//未设置优先级
const NoLanePriority = 0;
/**
* 一共有31条车道
*/
const TotalLanes = 31;
//没有车道,所有位都为0
const NoLanes = 0b0000000000000000000000000000000;
const NoLane = 0b0000000000000000000000000000000;
//同步车道,优先级最高
const SyncLane = 0b0000000000000000000000000000001;
const SyncBatchedLane = 0b0000000000000000000000000000010;
//离散用户交互车道 click
const InputDiscreteHydrationLane = 0b0000000000000000000000000000100;
const InputDiscreteLanes = 0b0000000000000000000000000011000;
//连续交互车道 mouseMove
const InputContinuousHydrationLane = 0b0000000000000000000000000100000;
const InputContinuousLanes = 0b0000000000000000000000011000000;
//默认车道
const DefaultHydrationLane = 0b0000000000000000000000100000000;
const DefaultLanes = 0b0000000000000000000111000000000;
//渐变车道
const TransitionHydrationLane = 0b0000000000000000001000000000000;
const TransitionLanes = 0b0000000001111111110000000000000;
//重试车道
const RetryLanes = 0b0000011110000000000000000000000;
const SomeRetryLane = 0b0000010000000000000000000000000;
//选择性水合车道
const SelectiveHydrationLane = 0b0000100000000000000000000000000;
//非空闲车道
const NonIdleLanes = 0b0000111111111111111111111111111;
const IdleHydrationLane = 0b0001000000000000000000000000000;
//空闲车道
const IdleLanes = 0b0110000000000000000000000000000;
//离屏车道
const OffscreenLane = 0b1000000000000000000000000000000;
/**
* 分离出所有比特位中最右边的1
* 分离出最高优先级的车道
* @param {*} lanes 车道
* @returns 车道
*/
function getHighestPriorityLane(lanes) {
return lanes & -lanes;
}
//console.log(getHighestPriorityLane(InputDiscreteLanes));//8 0b1000
//console.log('0000000000000000000000000011000');
//console.log('1111111111111111111111111101000');
/**
* 分离出所有比特位中最左边的1
* 分离出最低优先级的车道
* @param {*} lanes 车道
* @returns 车道
*/
function getLowestPriorityLane(lanes) {
const index = 31 - Math.clz32(lanes);
return index < 0 ? NoLanes : 1 << index;
}
console.log(getLowestPriorityLane(InputDiscreteLanes));//16 0b10000
console.log('0000000000000000000000000011000');
console.log(Math.clz32(InputDiscreteLanes));//27
console.log(31 - Math.clz32(InputDiscreteLanes));//4
ReactFiberLane.js
const NoLanes = 0b00;
const NoLane = 0b00;
const SyncLane = 0b01;
const SyncBatchedLane = 0b10;
/**
* 判断subset是不是set的子集
* @param {*} set
* @param {*} subset
* @returns
*/
function isSubsetOfLanes(set, subset) {
return (set & subset) === subset;
}
/**
* 合并两个车道
* @param {*} a
* @param {*} b
* @returns
*/
function mergeLanes(a, b) {
return a | b;
}
module.exports = {
NoLane,
NoLanes,
SyncLane,
SyncBatchedLane,
isSubsetOfLanes,
mergeLanes
}
ReactUpdateQueue.js
const { NoLane, NoLanes, isSubsetOfLanes, mergeLanes } = require('./ReactFiberLane');
function initializeUpdateQueue(fiber) {
const queue = {
baseState: fiber.memoizedState,//本次更新前该Fiber节点的state,Update基于该state计算更新后的state
firstBaseUpdate: null,//本次更新前该Fiber节点已保存的Update链表头
lastBaseUpdate: null,//本次更新前该Fiber节点已保存的Update链表尾
shared: {
//触发更新时,产生的Update会保存在shared.pending中形成单向环状链表
//当由Update计算state时这个环会被剪开并连接在lastBaseUpdate后面
pending: null
}
}
fiber.updateQueue = queue;
}
function enqueueUpdate(fiber, update) {
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
return;
}
const sharedQueue = updateQueue.shared;
const pending = sharedQueue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
}
/**
* 处理更新队列
* @param {*} fiber
* @param {*} renderLanes
*/
function processUpdateQueue(fiber, renderLanes) {
//获取此fiber上的更新队列
const queue = fiber.updateQueue;
//获取第一个更新
let firstBaseUpdate = queue.firstBaseUpdate;
let lastBaseUpdate = queue.lastBaseUpdate;
//判断一下是否在等待生效的的更新,如果有,变成base队列
let pendingQueue = queue.shared.pending;
if (pendingQueue !== null) {
//等待生效的队列是循环队列
queue.shared.pending = null;
//最后一个等待的更新 update4
const lastPendingUpdate = pendingQueue;
//第一个等待的更新 update1
const firstPendingUpdate = lastPendingUpdate.next;
//把环剪断,最后一个不再指向第一个
lastPendingUpdate.next = null;
//把等待生效的队列添加到base队列中
//如果base队列为空
if (lastBaseUpdate === null) {
firstBaseUpdate = firstPendingUpdate;
} else {//否则就把当前的更新队列添加到base队列的尾部
lastBaseUpdate.next = firstPendingUpdate;
}
//尾部也接上
lastBaseUpdate = lastPendingUpdate;
}
//开始计算新的状态
if (firstBaseUpdate !== null) {
//先获取老的值{number:0}
let newState = queue.baseState;
let newLanes = NoLanes;
let newBaseState = null;//新的基础状态
let newFirstBaseUpdate = null;//第一个跳过的更新
let newLastBaseUpdate = null;//新的最后一个基本更新
let update = firstBaseUpdate;//指向第一个更新
do {
//获取更新车道
const updateLane = update.lane;
//如果优先级不够,跳过这个更新,如果这是第一个跳过的更新,上一个状态和更新成为newBaseState和newFirstBaseUpdate
if (!isSubsetOfLanes(renderLanes, updateLane)) {
const clone = {
id: update.id,
lane: updateLane,
payload: update.payload
};
if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;// first=last=update1
newBaseState = newState;//0
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
//更新队列中的剩下的优先级
newLanes = mergeLanes(newLanes, updateLane);
} else {
//如果有足够的优先级 如果有曾经跳过的更新,仍然追加在后面
if (newLastBaseUpdate !== null) {
const clone = {
id: update.id,
//NoLane是所有的优先级的子集,永远不会被跳过
lane: NoLane,
payload: update.payload
};
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
newState = getStateFromUpdate(update, newState);
}
update = update.next;
if (!update) {
break;
}
} while (true);
if (!newLastBaseUpdate) {
newBaseState = newState;
}
queue.baseState = newBaseState;
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;
fiber.lanes = newLanes;
fiber.memoizedState = newState;
}
}
function getStateFromUpdate(update, prevState) {
const payload = update.payload;
let partialState = payload(prevState);
return Object.assign({}, prevState, partialState);
}
module.exports = {
initializeUpdateQueue,
enqueueUpdate,
processUpdateQueue
}
use.js
const { initializeUpdateQueue, enqueueUpdate, processUpdateQueue } = require('./ReactUpdateQueue');
const { SyncBatchedLane, SyncLane } = require('./ReactFiberLane');
//初始化fiber节点
let fiber = { memoizedState: { msg: '' } };
initializeUpdateQueue(fiber);
let update1 = { id: 'A', payload: (state) => ({ msg: state.msg + 'A' }), lane: SyncBatchedLane };
enqueueUpdate(fiber, update1);
let update2 = { id: 'B', payload: (state) => ({ msg: state.msg + 'B' }), lane: SyncLane };
enqueueUpdate(fiber, update2);
let update3 = { id: 'C', payload: (state) => ({ msg: state.msg + 'C' }), lane: SyncBatchedLane };
enqueueUpdate(fiber, update3);
let update4 = { id: 'D', payload: (state) => ({ msg: state.msg + 'D' }), lane: SyncLane };
enqueueUpdate(fiber, update4);
//以同步的优先级进行更新
processUpdateQueue(fiber, SyncLane);
console.log('memoizedState', fiber.memoizedState);
console.log('updateQueue', printQueue(fiber.updateQueue));
let update5 = { id: 'E', payload: (state) => ({ msg: state.msg + 'E' }), lane: SyncLane };
enqueueUpdate(fiber, update5);
processUpdateQueue(fiber, SyncLane);
console.log('memoizedState', fiber.memoizedState);
console.log('updateQueue', printQueue(fiber.updateQueue));
processUpdateQueue(fiber, SyncBatchedLane);
console.log('memoizedState', fiber.memoizedState);
console.log('updateQueue', printQueue(fiber.updateQueue));
let update6 = { id: 'F', payload: (state) => ({ msg: state.msg + 'F' }), lane: SyncLane };
enqueueUpdate(fiber, update6);
processUpdateQueue(fiber, SyncLane);
console.log('memoizedState', fiber.memoizedState);
console.log('updateQueue', printQueue(fiber.updateQueue));
function printQueue(queue) {
const { baseState, firstBaseUpdate } = queue
let state = baseState.msg + '|';
let update = firstBaseUpdate;
while (update) {
state += ((update.id) + "=>");
update = update.next;
}
state += "null";
console.log(state);
}
mkdir react182
cd react182
npm init -y
npm install vite @vitejs/plugin-react --save
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
export default defineConfig({
define: {
__DEV__: true,
__PROFILE__: true,
__UMD__: true,
__EXPERIMENTAL__: true,
},
resolve: {
alias: {
react: path.posix.resolve("src/react"),
"react-dom": path.posix.resolve("src/react-dom"),
"react-dom-bindings": path.posix.resolve("src/react-dom-bindings"),
"react-reconciler": path.posix.resolve("src/react-reconciler"),
scheduler: path.posix.resolve("src/scheduler"),
shared: path.posix.resolve("src/shared"),
},
},
plugins: [react()],
optimizeDeps: {
force: true,
},
});
jsconfig.json
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"react/*": ["src/react/*"],
"react-dom/*": ["src/react-dom/*"],
"react-dom-bindings/*": ["react-dom-bindings/*"],
"react-reconciler/*": ["src/react-reconciler/*"],
"scheduler/*": ["scheduler/*"],
"shared/*": ["src/shared/*"]
}
},
"exclude": ["node_modules", "dist"]
}
src\main.jsx
console.log("main");
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React18</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
package.json
{
"scripts": {
"dev": "vite"
}
}
main.jsx
let element = (
<h1>
hello<span style={{ color: "red" }}>world</span>
</h1>
);
console.log(element);
src\react\jsx-dev-runtime.js
export { jsxDEV } from "./src/jsx/ReactJSXElement";
src\react\src\jsx\ReactJSXElement.js
import hasOwnProperty from "shared/hasOwnProperty";
import { REACT_ELEMENT_TYPE } from "shared/ReactSymbols";
const RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true,
};
function hasValidRef(config) {
return config.ref !== undefined;
}
const ReactElement = (type, key, ref, props) => {
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
ref,
props,
};
return element;
};
export function jsxDEV(type, config, maybeKey) {
let propName;
const props = {};
let key = null;
let ref = null;
if (maybeKey !== undefined) {
key = "" + maybeKey;
}
if (hasValidRef(config)) {
ref = config.ref;
}
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
return ReactElement(type, key, ref, props);
}
src\shared\ReactSymbols.js
export const REACT_ELEMENT_TYPE = Symbol.for("react.element");
src\shared\hasOwnProperty.js
const { hasOwnProperty } = Object.prototype;
export default hasOwnProperty;
src\main.jsx
+import { createRoot } from "react-dom/client";
let element = (
<h1>
hello<span style={{ color: "red" }}>world</span>
</h1>
);
+const root = createRoot(document.getElementById("root"));
+console.log(root);
src\react-dom\client.js
export { createRoot } from "./src/client/ReactDOMRoot";
src\react-dom\src\client\ReactDOMRoot.js
import { createContainer } from "react-reconciler/src/ReactFiberReconciler";
function ReactDOMRoot(internalRoot) {
this._internalRoot = internalRoot;
}
export function createRoot(container) {
const root = createContainer(container);
return new ReactDOMRoot(root);
}
src\react-reconciler\src\ReactFiberReconciler.js
import { createFiberRoot } from "./ReactFiberRoot";
export function createContainer(containerInfo) {
return createFiberRoot(containerInfo);
}
src\react-reconciler\src\ReactFiberRoot.js
function FiberRootNode(containerInfo) {
this.containerInfo = containerInfo;
}
export function createFiberRoot(containerInfo) {
const root = new FiberRootNode(containerInfo);
return root;
}
src\react-reconciler\src\ReactFiberRoot.js
+import { createHostRootFiber } from "./ReactFiber";
function FiberRootNode(containerInfo) {
this.containerInfo = containerInfo;
}
export function createFiberRoot(containerInfo) {
const root = new FiberRootNode(containerInfo);
+ const uninitializedFiber = createHostRootFiber();
+ root.current = uninitializedFiber;
+ uninitializedFiber.stateNode = root;
return root;
}
src\react-reconciler\src\ReactFiber.js
import { HostRoot } from "./ReactWorkTags";
import { NoFlags } from "./ReactFiberFlags";
export function FiberNode(tag, pendingProps, key) {
this.tag = tag;
this.key = key;
this.type = null;
this.stateNode = null;
this.return = null;
this.child = null;
this.sibling = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.alternate = null;
}
function createFiber(tag, pendingProps, key) {
return new FiberNode(tag, pendingProps, key);
}
export function createHostRootFiber() {
return createFiber(HostRoot, null, null);
}
src\react-reconciler\src\ReactWorkTags.js
export const HostRoot = 3;
src\react-reconciler\src\ReactFiberFlags.js
export const NoFlags = 0b00000000000000000000000000;
src\react-reconciler\src\ReactFiberRoot.js
import { createHostRootFiber } from "./ReactFiber";
+import { initializeUpdateQueue } from "./ReactFiberClassUpdateQueue";
function FiberRootNode(containerInfo) {
this.containerInfo = containerInfo;
}
export function createFiberRoot(containerInfo) {
const root = new FiberRootNode(containerInfo);
const uninitializedFiber = createHostRootFiber();
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
+ initializeUpdateQueue(uninitializedFiber);
return root;
}
src\react-reconciler\src\ReactFiberClassUpdateQueue.js
export function initializeUpdateQueue(fiber) {
const queue = {
shared: {
pending: null,
},
};
fiber.updateQueue = queue;
}
src\main.jsx
import { createRoot } from "react-dom/client";
let element = (
<h1>
hello<span style={{ color: "red" }}>world</span>
</h1>
);
const root = createRoot(document.getElementById("root"));
+root.render(element);
src\react-dom\src\client\ReactDOMRoot.js
import {
createContainer,
+ updateContainer,
} from "react-reconciler/src/ReactFiberReconciler";
function ReactDOMRoot(internalRoot) {
this._internalRoot = internalRoot;
}
+ReactDOMRoot.prototype.render = function render(children) {
+ const root = this._internalRoot;
+ root.containerInfo.innerHTML = "";
+ updateContainer(children, root);
+};
export function createRoot(container) {
const root = createContainer(container);
return new ReactDOMRoot(root);
}
src\react-reconciler\src\ReactFiberReconciler.js
import { createFiberRoot } from "./ReactFiberRoot";
+import { createUpdate, enqueueUpdate } from "./ReactFiberClassUpdateQueue";
export function createContainer(containerInfo) {
return createFiberRoot(containerInfo);
}
+export function updateContainer(element, container) {
+ const current = container.current;
+ const update = createUpdate();
+ update.payload = { element };
+ const root = enqueueUpdate(current, update);
+ console.log(root);
+}
src\react-reconciler\src\ReactFiberClassUpdateQueue.js
+import { markUpdateLaneFromFiberToRoot } from "./ReactFiberConcurrentUpdates";
+export const UpdateState = 0;
export function initializeUpdateQueue(fiber) {
const queue = {
shared: {
pending: null,
},
};
fiber.updateQueue = queue;
}
+export function createUpdate() {
+ const update = { tag: UpdateState };
+ return update;
+}
+export function enqueueUpdate(fiber, update) {
+ const updateQueue = fiber.updateQueue;
+ const sharedQueue = updateQueue.shared;
+ const pending = sharedQueue.pending;
+ if (pending === null) {
+ update.next = update;
+ } else {
+ update.next = pending.next;
+ pending.next = update;
+ }
+ updateQueue.shared.pending = update;
+ return markUpdateLaneFromFiberToRoot(fiber);
+}
src\react-reconciler\src\ReactFiberConcurrentUpdates.js
import { HostRoot } from "./ReactWorkTags";
export function markUpdateLaneFromFiberToRoot(sourceFiber) {
let node = sourceFiber;
let parent = sourceFiber.return;
while (parent !== null) {
node = parent;
parent = parent.return;
}
if (node.tag === HostRoot) {
const root = node.stateNode;
return root;
}
return null;
}
src\react-reconciler\src\ReactFiberReconciler.js
import { createFiberRoot } from "./ReactFiberRoot";
import { createUpdate, enqueueUpdate } from "./ReactFiberClassUpdateQueue";
+import { scheduleUpdateOnFiber } from "./ReactFiberWorkLoop";
export function createContainer(containerInfo) {
return createFiberRoot(containerInfo);
}
export function updateContainer(element, container) {
const current = container.current;
const update = createUpdate();
update.payload = { element };
const root = enqueueUpdate(current, update);
+ scheduleUpdateOnFiber(root);
}
src\react-reconciler\src\ReactFiberWorkLoop.js
import { scheduleCallback } from "scheduler";
export function scheduleUpdateOnFiber(root) {
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}
function performConcurrentWorkOnRoot(root) {
console.log("performConcurrentWorkOnRoot");
}
src\scheduler\index.js
export * from "./src/forks/Scheduler";
src\scheduler\src\forks\Scheduler.js
export function scheduleCallback(callback) {
requestIdleCallback(callback);
}
src\react-reconciler\src\ReactFiberWorkLoop.js
import { scheduleCallback } from "scheduler";
+import { createWorkInProgress } from "./ReactFiber";
+let workInProgress = null;
export function scheduleUpdateOnFiber(root) {
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}
function performConcurrentWorkOnRoot(root) {
+ renderRootSync(root);
}
+function prepareFreshStack(root) {
+ workInProgress = createWorkInProgress(root.current, null);
+ console.log(workInProgress);
+}
+function renderRootSync(root) {
+ prepareFreshStack(root);
+}
src\react-reconciler\src\ReactFiber.js
import { HostRoot } from "./ReactWorkTags";
import { NoFlags } from "./ReactFiberFlags";
export function FiberNode(tag, pendingProps, key) {
this.tag = tag;
this.key = key;
this.type = null;
this.stateNode = null;
this.return = null;
this.child = null;
this.sibling = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.alternate = null;
}
function createFiber(tag, pendingProps, key) {
return new FiberNode(tag, pendingProps, key);
}
export function createHostRootFiber() {
return createFiber(HostRoot, null, null);
}
+// We use a double buffering pooling technique because we know that we'll
+// only ever need at most two versions of a tree. We pool the "other" unused
+// node that we're free to reuse. This is lazily created to avoid allocating
+// extra objects for things that are never updated. It also allow us to
+// reclaim the extra memory if needed.
+//我们使用双缓冲池技术,因为我们知道一棵树最多只需要两个版本
+//我们将“其他”未使用的我们可以自由重用的节点
+//这是延迟创建的,以避免分配从未更新的内容的额外对象。它还允许我们如果需要,回收额外的内+存
+export function createWorkInProgress(current, pendingProps) {
+ let workInProgress = current.alternate;
+ if (workInProgress === null) {
+ workInProgress = createFiber(current.tag, pendingProps, current.key);
+ workInProgress.type = current.type;
+ workInProgress.stateNode = current.stateNode;
+ workInProgress.alternate = current;
+ current.alternate = workInProgress;
+ } else {
+ workInProgress.pendingProps = pendingProps;
+ workInProgress.type = current.type;
+ workInProgress.flags = NoFlags;
+ workInProgress.subtreeFlags = NoFlags;
+ }
+ workInProgress.child = current.child;
+ workInProgress.memoizedProps = current.memoizedProps;
+ workInProgress.memoizedState = current.memoizedState;
+ workInProgress.updateQueue = current.updateQueue;
+ workInProgress.sibling = current.sibling;
+ workInProgress.index = current.index;
+ return workInProgress;
+}
src\react-reconciler\src\ReactFiberWorkLoop.js
import { scheduleCallback } from "scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
let workInProgress = null;
export function scheduleUpdateOnFiber(root) {
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}
function performConcurrentWorkOnRoot(root) {
renderRootSync(root);
}
function prepareFreshStack(root) {
workInProgress = createWorkInProgress(root.current, null);
}
function renderRootSync(root) {
prepareFreshStack(root);
+ workLoopSync();
}
+function workLoopSync() {
+ while (workInProgress !== null) {
+ performUnitOfWork(workInProgress);
+ }
+}
+function performUnitOfWork(unitOfWork) {
+ const current = unitOfWork.alternate;
+ const next = beginWork(current, unitOfWork);
+ unitOfWork.memoizedProps = unitOfWork.pendingProps;
+ if (next === null) {
+ //completeUnitOfWork(unitOfWork);
+ workInProgress = null;
+ } else {
+ workInProgress = next;
+ }
+}
src\react-reconciler\src\ReactFiberBeginWork.js
import { HostRoot, HostComponent, HostText } from "./ReactWorkTags";
import { processUpdateQueue } from "./ReactFiberClassUpdateQueue";
import { mountChildFibers, reconcileChildFibers } from "./ReactChildFiber";
import { shouldSetTextContent } from "react-dom-bindings/src/client/ReactDOMHostConfig";
import logger, { indent } from "shared/logger";
function reconcileChildren(current, workInProgress, nextChildren) {
if (current === null) {
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren);
} else {
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren);
}
}
function updateHostRoot(current, workInProgress) {
processUpdateQueue(workInProgress);
const nextState = workInProgress.memoizedState;
const nextChildren = nextState.element;
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
function updateHostComponent(current, workInProgress) {
const { type } = workInProgress;
const nextProps = workInProgress.pendingProps;
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
nextChildren = null;
}
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
export function beginWork(current, workInProgress) {
logger(" ".repeat(indent.number) + "beginWork", workInProgress);
switch (workInProgress.tag) {
case HostRoot:
return updateHostRoot(current, workInProgress);
case HostComponent:
return updateHostComponent(current, workInProgress);
case HostText:
default:
return null;
}
}
src\react-reconciler\src\ReactWorkTags.js
export const HostRoot = 3;
+export const IndeterminateComponent = 2;
+export const HostComponent = 5;
+export const HostText = 6;
src\react-reconciler\src\ReactFiberClassUpdateQueue.js
import { markUpdateLaneFromFiberToRoot } from "./ReactFiberConcurrentUpdates";
+import assign from "shared/assign";
export const UpdateState = 0;
export function initializeUpdateQueue(fiber) {
const queue = {
shared: {
pending: null,
},
};
fiber.updateQueue = queue;
}
export function createUpdate() {
const update = { tag: UpdateState };
return update;
}
export function enqueueUpdate(fiber, update) {
const updateQueue = fiber.updateQueue;
const sharedQueue = updateQueue.shared;
const pending = sharedQueue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
updateQueue.shared.pending = update;
return markUpdateLaneFromFiberToRoot(fiber);
}
+function getStateFromUpdate(update, prevState) {
+ switch (update.tag) {
+ case UpdateState: {
+ const { payload } = update;
+ const partialState = payload;
+ return assign({}, prevState, partialState);
+ }
+ default:
+ return prevState;
+ }
+}
+export function processUpdateQueue(workInProgress) {
+ const queue = workInProgress.updateQueue;
+ const pendingQueue = queue.shared.pending;
+ if (pendingQueue !== null) {
+ queue.shared.pending = null;
+ const lastPendingUpdate = pendingQueue;
+ const firstPendingUpdate = lastPendingUpdate.next;
+ lastPendingUpdate.next = null;
+ let newState = workInProgress.memoizedState;
+ let update = firstPendingUpdate;
+ while (update) {
+ newState = getStateFromUpdate(update, newState);
+ update = update.next;
+ }
+ workInProgress.memoizedState = newState;
+ }
+}
src\react-reconciler\src\ReactChildFiber.js
import { REACT_ELEMENT_TYPE } from "shared/ReactSymbols";
import isArray from "shared/isArray";
import { createFiberFromElement, FiberNode, createFiberFromText } from "./ReactFiber";
import { Placement } from "./ReactFiberFlags";
import { HostText } from "./ReactWorkTags";
function createChildReconciler(shouldTrackSideEffects) {
function reconcileSingleElement(returnFiber, currentFirstChild, element) {
const created = createFiberFromElement(element);
created.return = returnFiber;
return created;
}
function placeSingleChild(newFiber) {
if (shouldTrackSideEffects) newFiber.flags |= Placement;
return newFiber;
}
function reconcileSingleTextNode(returnFiber, currentFirstChild, content) {
const created = new FiberNode(HostText, { content }, null);
created.return = returnFiber;
return created;
}
function createChild(returnFiber, newChild) {
if ((typeof newChild === "string" && newChild !== "") || typeof newChild === "number") {
const created = createFiberFromText(`${newChild}`);
created.return = returnFiber;
return created;
}
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(newChild);
created.return = returnFiber;
return created;
}
default:
break;
}
}
return null;
}
function placeChild(newFiber, newIndex) {
newFiber.index = newIndex;
if (shouldTrackSideEffects) newFiber.flags |= Placement;
}
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) {
let resultingFirstChild = null;
let previousNewFiber = null;
let newIdx = 0;
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx]);
if (newFiber === null) {
continue;
}
placeChild(newFiber, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
function reconcileChildFibers(returnFiber, currentFirstChild, newChild) {
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild));
}
default:
break;
}
if (isArray(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild);
}
}
if (typeof newChild === "string") {
return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, newChild));
}
return null;
}
return reconcileChildFibers;
}
export const reconcileChildFibers = createChildReconciler(true);
export const mountChildFibers = createChildReconciler(false);
src\react-dom-bindings\src\client\ReactDOMHostConfig.js
export function shouldSetTextContent(type, props) {
return typeof props.children === "string" || typeof props.children === "number";
}
src\shared\logger.js
import * as ReactWorkTags from "react-reconciler/src/ReactWorkTags";
const ReactWorkTagsMap = new Map();
for (let tag in ReactWorkTags) {
ReactWorkTagsMap.set(ReactWorkTags[tag], tag);
}
export default function logger(prefix, workInProgress) {
let tagValue = workInProgress.tag;
let tagName = ReactWorkTagsMap.get(tagValue);
let str = ` ${tagName} `;
if (tagName === "HostComponent") {
str += ` ${workInProgress.type} `;
} else if (tagName === "HostText") {
str += ` ${workInProgress.pendingProps} `;
}
console.log(`${prefix} ${str}`);
}
let indent = { number: 0 };
export { indent };
src\shared\assign.js
const { assign } = Object;
export default assign;
src\shared\isArray.js
const { isArray } = Array;
export default isArray;
src\react-reconciler\src\ReactFiber.js
import {
HostRoot,
+ IndeterminateComponent,
+ HostComponent,
+ HostText,
} from "./ReactWorkTags";
import { NoFlags } from "./ReactFiberFlags";
export function FiberNode(tag, pendingProps, key) {
this.tag = tag;
this.key = key;
this.type = null;
this.stateNode = null;
this.return = null;
this.child = null;
this.sibling = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.alternate = null;
}
function createFiber(tag, pendingProps, key) {
return new FiberNode(tag, pendingProps, key);
}
export function createHostRootFiber() {
return createFiber(HostRoot, null, null);
}
// We use a double buffering pooling technique because we know that we'll
// only ever need at most two versions of a tree. We pool the "other" unused
// node that we're free to reuse. This is lazily created to avoid allocating
// extra objects for things that are never updated. It also allow us to
// reclaim the extra memory if needed.
//我们使用双缓冲池技术,因为我们知道一棵树最多只需要两个版本
//我们将“其他”未使用的我们可以自由重用的节点
//这是延迟创建的,以避免分配从未更新的内容的额外对象。它还允许我们如果需要,回收额外的内存
export function createWorkInProgress(current, pendingProps) {
let workInProgress = current.alternate;
if (workInProgress === null) {
workInProgress = createFiber(current.tag, pendingProps, current.key);
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
workInProgress.type = current.type;
workInProgress.flags = NoFlags;
workInProgress.subtreeFlags = NoFlags;
}
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
return workInProgress;
}
+export function createFiberFromTypeAndProps(type, key, pendingProps) {
+ let fiberTag = IndeterminateComponent;
+ if (typeof type === "string") {
+ fiberTag = HostComponent;
+ }
+ const fiber = createFiber(fiberTag, pendingProps, key);
+ fiber.type = type;
+ return fiber;
+}
+export function createFiberFromElement(element) {
+ const { type } = element;
+ const { key } = element;
+ const pendingProps = element.props;
+ const fiber = createFiberFromTypeAndProps(type, key, pendingProps);
+ return fiber;
+}
+
+export function createFiberFromText(content) {
+ const fiber = createFiber(HostText, content, null);
+ return fiber;
+}
src\react-reconciler\src\ReactFiberFlags.js
export const NoFlags = 0b00000000000000000000000000;
+export const Placement = 0b00000000000000000000000010;
+export const MutationMask = Placement;
src\react-reconciler\src\ReactFiberWorkLoop.js
import { scheduleCallback } from "scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
+import { completeWork } from "./ReactFiberCompleteWork";
let workInProgress = null;
export function scheduleUpdateOnFiber(root) {
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}
function performConcurrentWorkOnRoot(root) {
renderRootSync(root);
}
function prepareFreshStack(root) {
workInProgress = createWorkInProgress(root.current, null);
}
function renderRootSync(root) {
prepareFreshStack(root);
workLoopSync();
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
+ completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
+function completeUnitOfWork(unitOfWork) {
+ let completedWork = unitOfWork;
+ do {
+ const current = completedWork.alternate;
+ const returnFiber = completedWork.return;
+ completeWork(current, completedWork);
+ const siblingFiber = completedWork.sibling;
+ if (siblingFiber !== null) {
+ workInProgress = siblingFiber;
+ return;
+ }
+ completedWork = returnFiber;
+ workInProgress = completedWork;
+ } while (completedWork !== null);
+}
src\react-reconciler\src\ReactFiberCompleteWork.js
import {
appendInitialChild,
createInstance,
createTextInstance,
finalizeInitialChildren,
} from "react-dom-bindings/src/client/ReactDOMHostConfig";
import { HostComponent, HostRoot, HostText } from "./ReactWorkTags";
import { NoFlags } from "./ReactFiberFlags";
import logger, { indent } from "shared/logger";
function bubbleProperties(completedWork) {
let subtreeFlags = NoFlags;
let child = completedWork.child;
while (child !== null) {
subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;
child = child.sibling;
}
completedWork.subtreeFlags |= subtreeFlags;
}
function appendAllChildren(parent, workInProgress) {
// 我们只有创建的顶级fiber,但需要递归其子节点来查找所有终端节点
let node = workInProgress.child;
while (node !== null) {
// 如果是原生节点,直接添加到父节点上
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
// 再看看第一个节节点是不是原生节点
} else if (node.child !== null) {
// node.child.return = node
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
// 如果没有弟弟就找父亲的弟弟
while (node.sibling === null) {
// 如果找到了根节点或者回到了原节点结束
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
// node.sibling.return = node.return
// 下一个弟弟节点
node = node.sibling;
}
}
export function completeWork(current, workInProgress) {
indent.number -= 2;
logger(" ".repeat(indent.number) + "completeWork", workInProgress);
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent: {
const { type } = workInProgress;
const instance = createInstance(type, newProps, workInProgress);
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;
finalizeInitialChildren(instance, type, newProps);
bubbleProperties(workInProgress);
break;
}
case HostRoot:
bubbleProperties(workInProgress);
break;
case HostText: {
const newText = newProps;
workInProgress.stateNode = createTextInstance(newText);
bubbleProperties(workInProgress);
break;
}
default:
break;
}
}
src\react-dom-bindings\src\client\ReactDOMHostConfig.js
+import { setInitialProperties } from "./ReactDOMComponent";
export function shouldSetTextContent(type, props) {
return (
typeof props.children === "string" || typeof props.children === "number"
);
}
+export const appendInitialChild = (parent, child) => {
+ parent.appendChild(child);
+};
+export const createInstance = (type, props, internalInstanceHandle) => {
+ const domElement = document.createElement(type);
+ return domElement;
+};
+export const createTextInstance = (content) => document.createTextNode+(content);
+export function finalizeInitialChildren(domElement, type, props) {
+ setInitialProperties(domElement, type, props);
+}
src\react-dom-bindings\src\client\ReactDOMComponent.js
import { setValueForStyles } from "./CSSPropertyOperations";
import setTextContent from "./setTextContent";
import { setValueForProperty } from "./DOMPropertyOperations";
const CHILDREN = "children";
const STYLE = "style";
function setInitialDOMProperties(tag, domElement, nextProps) {
for (const propKey in nextProps) {
if (nextProps.hasOwnProperty(propKey)) {
const nextProp = nextProps[propKey];
if (propKey === STYLE) {
setValueForStyles(domElement, nextProp);
} else if (propKey === CHILDREN) {
if (typeof nextProp === "string") {
setTextContent(domElement, nextProp);
} else if (typeof nextProp === "number") {
setTextContent(domElement, `${nextProp}`);
}
} else if (nextProp != null) {
setValueForProperty(domElement, propKey, nextProp);
}
}
}
}
export function setInitialProperties(domElement, tag, props) {
setInitialDOMProperties(tag, domElement, props);
}
src\react-dom-bindings\src\client\CSSPropertyOperations.js
export function setValueForStyles(node, styles) {
const { style } = node;
for (const styleName in styles) {
if (styles.hasOwnProperty(styleName)) {
const styleValue = styles[styleName];
style[styleName] = styleValue;
}
}
}
src\react-dom-bindings\src\client\setTextContent.js
function setTextContent(node, text) {
node.textContent = text;
}
export default setTextContent;
src\react-dom-bindings\src\client\DOMPropertyOperations.js
export function setValueForProperty(node, name, value) {
if (value === null) {
node.removeAttribute(name);
} else {
node.setAttribute(name, value);
}
}
src\react-reconciler\src\ReactFiberWorkLoop.js
import { scheduleCallback } from "scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
import { completeWork } from "./ReactFiberCompleteWork";
+import { MutationMask, NoFlags } from "./ReactFiberFlags";
let workInProgress = null;
export function scheduleUpdateOnFiber(root) {
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}
function performConcurrentWorkOnRoot(root) {
renderRootSync(root);
+ const finishedWork = root.current.alternate;
+ printFiber(finishedWork);
+ console.log(`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`);
+ root.finishedWork = finishedWork;
+ commitRoot(root);
}
+function commitRoot(root) {
+ const { finishedWork } = root;
+ const subtreeHasEffects =
+ (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
+ const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
+ if (subtreeHasEffects || rootHasEffect) {
+ console.log("commitRoot");
+ }
+ root.current = finishedWork;
+}
+function printFiber(fiber) {
+ /*
+ fiber.flags &= ~Forked;
+ fiber.flags &= ~PlacementDEV;
+ fiber.flags &= ~Snapshot;
+ fiber.flags &= ~PerformedWork;
+ */
+ if (fiber.flags !== 0) {
+ console.log(
+ getFlags(fiber.flags),
+ getTag(fiber.tag),
+ typeof fiber.type === "function" ? fiber.type.name : fiber.type,
+ fiber.memoizedProps
+ );
+ if (fiber.deletions) {
+ for (let i = 0; i < fiber.deletions.length; i++) {
+ const childToDelete = fiber.deletions[i];
+ console.log(getTag(childToDelete.tag), childToDelete.type, childToDelete.+memoizedProps);
+ }
+ }
+ }
+ let child = fiber.child;
+ while (child) {
+ printFiber(child);
+ child = child.sibling;
+ }
+}
+function getTag(tag) {
+ switch (tag) {
+ case FunctionComponent:
+ return `FunctionComponent`;
+ case HostRoot:
+ return `HostRoot`;
+ case HostComponent:
+ return `HostComponent`;
+ case HostText:
+ return HostText;
+ default:
+ return tag;
+ }
+}
+function getFlags(flags) {
+ if (flags === (Update | Placement | ChildDeletion)) {
+ return `自己移动和子元素有删除`;
+ }
+ if (flags === (ChildDeletion | Update)) {
+ return `自己有更新和子元素有删除`;
+ }
+ if (flags === ChildDeletion) {
+ return `子元素有删除`;
+ }
+ if (flags === (Placement | Update)) {
+ return `移动并更新`;
+ }
+ if (flags === Placement) {
+ return `插入`;
+ }
+ if (flags === Update) {
+ return `更新`;
+ }
+ return flags;
+}
function prepareFreshStack(root) {
workInProgress = createWorkInProgress(root.current, null);
}
function renderRootSync(root) {
prepareFreshStack(root);
workLoopSync();
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
completeWork(current, completedWork);
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
}
src\react-reconciler\src\ReactFiberWorkLoop.js
import { scheduleCallback } from "scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
import { completeWork } from "./ReactFiberCompleteWork";
import { MutationMask, NoFlags } from "./ReactFiberFlags";
+import { commitMutationEffectsOnFiber } from "./ReactFiberCommitWork";
let workInProgress = null;
export function scheduleUpdateOnFiber(root) {
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}
function performConcurrentWorkOnRoot(root) {
renderRootSync(root);
const finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
commitRoot(root);
}
function commitRoot(root) {
const { finishedWork } = root;
const subtreeHasEffects =
(finishedWork.subtreeFlags & MutationMask) !== NoFlags;
const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
+ commitMutationEffectsOnFiber(finishedWork, root);
}
root.current = finishedWork;
}
function prepareFreshStack(root) {
workInProgress = createWorkInProgress(root.current, null);
}
function renderRootSync(root) {
prepareFreshStack(root);
workLoopSync();
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
completeWork(current, completedWork);
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
}
src\react-reconciler\src\ReactFiberCommitWork.js
import { HostRoot, HostComponent, HostText } from "./ReactWorkTags";
import { MutationMask, Placement } from "./ReactFiberFlags";
function recursivelyTraverseMutationEffects(root, parentFiber) {
if (parentFiber.subtreeFlags & MutationMask) {
let { child } = parentFiber;
while (child !== null) {
commitMutationEffectsOnFiber(child, root);
child = child.sibling;
}
}
}
function commitPlacement(finishedWork) {
console.log("commitPlacement", finishedWork);
}
function commitReconciliationEffects(finishedWork) {
const { flags } = finishedWork;
if (flags & Placement) {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}
}
export function commitMutationEffectsOnFiber(finishedWork, root) {
switch (finishedWork.tag) {
case HostRoot:
case HostComponent:
case HostText: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
default: {
break;
}
}
}
src\react-reconciler\src\ReactFiberCommitWork.js
import { HostRoot, HostComponent, HostText } from "./ReactWorkTags";
import { MutationMask, Placement } from "./ReactFiberFlags";
+import {
+ insertBefore,
+ appendChild,
+} from "react-dom-bindings/src/client/ReactDOMHostConfig";
function recursivelyTraverseMutationEffects(root, parentFiber) {
if (parentFiber.subtreeFlags & MutationMask) {
let { child } = parentFiber;
while (child !== null) {
commitMutationEffectsOnFiber(child, root);
child = child.sibling;
}
}
}
+function isHostParent(fiber) {
+ return fiber.tag === HostComponent || fiber.tag === HostRoot;
+}
+function getHostParentFiber(fiber) {
+ let parent = fiber.return;
+ while (parent !== null) {
+ if (isHostParent(parent)) {
+ return parent;
+ }
+ parent = parent.return;
+ }
+ return parent;
+}
+function insertOrAppendPlacementNode(node, before, parent) {
+ const { tag } = node;
+ const isHost = tag === HostComponent || tag === HostText;
+ if (isHost) {
+ const { stateNode } = node;
+ if (before) {
+ insertBefore(parent, stateNode, before);
+ } else {
+ appendChild(parent, stateNode);
+ }
+ } else {
+ const { child } = node;
+ if (child !== null) {
+ insertOrAppendPlacementNode(child, before, parent);
+ let { sibling } = child;
+ while (sibling !== null) {
+ insertOrAppendPlacementNode(sibling, before, parent);
+ sibling = sibling.sibling;
+ }
+ }
+ }
+}
+function getHostSibling(fiber) {
+ let node = fiber;
+ siblings: while (true) {
+ // 如果我们没有找到任何东西,让我们试试下一个弟弟
+ while (node.sibling === null) {
+ if (node.return === null || isHostParent(node.return)) {
+ // 如果我们是根Fiber或者父亲是原生节点,我们就是最后的弟弟
+ return null;
+ }
+ node = node.return;
+ }
+ // node.sibling.return = node.return
+ node = node.sibling;
+ while (node.tag !== HostComponent && node.tag !== HostText) {
+ // 如果它不是原生节点,并且,我们可能在其中有一个原生节点
+ // 试着向下搜索,直到找到为止
+ if (node.flags & Placement) {
+ // 如果我们没有孩子,可以试试弟弟
+ continue siblings;
+ } else {
+ // node.child.return = node
+ node = node.child;
+ }
+ } // Check if this host node is stable or about to be placed.
+ // 检查此原生节点是否稳定可以放置
+ if (!(node.flags & Placement)) {
+ // 找到它了!
+ return node.stateNode;
+ }
+ }
+}
function commitPlacement(finishedWork) {
+ const parentFiber = getHostParentFiber(finishedWork);
+ switch (parentFiber.tag) {
+ case HostComponent: {
+ const parent = parentFiber.stateNode;
+ const before = getHostSibling(finishedWork);
+ insertOrAppendPlacementNode(finishedWork, before, parent);
+ break;
+ }
+ case HostRoot: {
+ const parent = parentFiber.stateNode.containerInfo;
+ const before = getHostSibling(finishedWork);
+ insertOrAppendPlacementNode(finishedWork, before, parent);
+ break;
+ }
+ default:
+ break;
+ }
}
function commitReconciliationEffects(finishedWork) {
const { flags } = finishedWork;
if (flags & Placement) {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}
}
export function commitMutationEffectsOnFiber(finishedWork, root) {
switch (finishedWork.tag) {
case HostRoot:
case HostComponent:
case HostText: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
default: {
break;
}
}
}
src\react-dom-bindings\src\client\ReactDOMHostConfig.js
import { setInitialProperties } from "./ReactDOMComponent";
export function shouldSetTextContent(type, props) {
return (
typeof props.children === "string" || typeof props.children === "number"
);
}
export const appendInitialChild = (parent, child) => {
parent.appendChild(child);
};
export const createInstance = (type, props, internalInstanceHandle) => {
const domElement = document.createElement(type);
return domElement;
};
export const createTextInstance = (content) => document.createTextNode(content);
export function finalizeInitialChildren(domElement, type, props) {
setInitialProperties(domElement, type, props);
}
+export function appendChild(parentInstance, child) {
+ parentInstance.appendChild(child);
+}
+export function insertBefore(parentInstance, child, beforeChild) {
+ parentInstance.insertBefore(child, beforeChild);
+}
src\main.jsx
import { createRoot } from "react-dom/client";
+function FunctionComponent() {
+ return (
+ <h1>
+ hello<span style={{ color: "red" }}>world</span>
+ </h1>
+ );
+}
+let element = <FunctionComponent />;
const root = createRoot(document.getElementById("root"));
root.render(element);
src\react-reconciler\src\ReactWorkTags.js
+export const FunctionComponent = 0;
+export const IndeterminateComponent = 2;
export const HostRoot = 3;
export const HostComponent = 5;
export const HostText = 6;
src\react-reconciler\src\ReactFiberBeginWork.js
import {
HostRoot,
HostComponent,
HostText,
+ IndeterminateComponent,
+ FunctionComponent,
} from "./ReactWorkTags";
import { processUpdateQueue } from "./ReactFiberClassUpdateQueue";
import { mountChildFibers, reconcileChildFibers } from "./ReactChildFiber";
import { shouldSetTextContent } from "react-dom-bindings/src/client/ReactDOMHostConfig";
import logger, { indent } from "shared/logger";
+import { renderWithHooks } from "react-reconciler/src/ReactFiberHooks";
function reconcileChildren(current, workInProgress, nextChildren) {
if (current === null) {
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren);
} else {
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren
);
}
}
function updateHostRoot(current, workInProgress) {
processUpdateQueue(workInProgress);
const nextState = workInProgress.memoizedState;
const nextChildren = nextState.element;
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
function updateHostComponent(current, workInProgress) {
const { type } = workInProgress;
const nextProps = workInProgress.pendingProps;
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
nextChildren = null;
}
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
+function mountIndeterminateComponent(_current, workInProgress, Component) {
+ const props = workInProgress.pendingProps;
+ const value = renderWithHooks(null, workInProgress, Component, props);
+ workInProgress.tag = FunctionComponent;
+ reconcileChildren(null, workInProgress, value);
+ return workInProgress.child;
+}
export function beginWork(current, workInProgress) {
logger(" ".repeat(indent.number) + "beginWork", workInProgress);
indent.number += 2;
switch (workInProgress.tag) {
+ case IndeterminateComponent: {
+ return mountIndeterminateComponent(
+ current,
+ workInProgress,
+ workInProgress.type
+ );
+ }
case HostRoot:
return updateHostRoot(current, workInProgress);
case HostComponent:
return updateHostComponent(current, workInProgress);
case HostText:
default:
return null;
}
}
src\react-reconciler\src\ReactFiberHooks.js
export function renderWithHooks(current, workInProgress, Component, props) {
const children = Component(props);
return children;
}
src\react-reconciler\src\ReactFiberCommitWork.js
import {
HostRoot,
HostComponent,
HostText,
+ FunctionComponent,
} from "./ReactWorkTags";
import { MutationMask, Placement } from "./ReactFiberFlags";
import {
insertBefore,
appendChild,
} from "react-dom-bindings/src/client/ReactDOMHostConfig";
function recursivelyTraverseMutationEffects(root, parentFiber) {
if (parentFiber.subtreeFlags & MutationMask) {
let { child } = parentFiber;
while (child !== null) {
commitMutationEffectsOnFiber(child, root);
child = child.sibling;
}
}
}
function isHostParent(fiber) {
return fiber.tag === HostComponent || fiber.tag === HostRoot;
}
function getHostParentFiber(fiber) {
let parent = fiber.return;
while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}
parent = parent.return;
}
return parent;
}
function insertOrAppendPlacementNode(node, before, parent) {
const { tag } = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost) {
const { stateNode } = node;
if (before) {
insertBefore(parent, stateNode, before);
} else {
appendChild(parent, stateNode);
}
} else {
const { child } = node;
if (child !== null) {
insertOrAppendPlacementNode(child, before, parent);
let { sibling } = child;
while (sibling !== null) {
insertOrAppendPlacementNode(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}
function getHostSibling(fiber) {
let node = fiber;
siblings: while (true) {
// 如果我们没有找到任何东西,让我们试试下一个弟弟
while (node.sibling === null) {
if (node.return === null || isHostParent(node.return)) {
// 如果我们是根Fiber或者父亲是原生节点,我们就是最后的弟弟
return null;
}
node = node.return;
}
// node.sibling.return = node.return
node = node.sibling;
while (node.tag !== HostComponent && node.tag !== HostText) {
// 如果它不是原生节点,并且,我们可能在其中有一个原生节点
// 试着向下搜索,直到找到为止
if (node.flags & Placement) {
// 如果我们没有孩子,可以试试弟弟
continue siblings;
} else {
// node.child.return = node
node = node.child;
}
} // Check if this host node is stable or about to be placed.
// 检查此原生节点是否稳定可以放置
if (!(node.flags & Placement)) {
// 找到它了!
return node.stateNode;
}
}
}
function commitPlacement(finishedWork) {
const parentFiber = getHostParentFiber(finishedWork);
switch (parentFiber.tag) {
case HostComponent: {
const parent = parentFiber.stateNode;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
case HostRoot: {
const parent = parentFiber.stateNode.containerInfo;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
default:
break;
}
}
function commitReconciliationEffects(finishedWork) {
const { flags } = finishedWork;
if (flags & Placement) {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}
}
export function commitMutationEffectsOnFiber(finishedWork, root) {
switch (finishedWork.tag) {
case HostRoot:
+ case FunctionComponent:
case HostComponent:
case HostText: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
default: {
break;
}
}
}
src\main.jsx
import { createRoot } from "react-dom/client";
function FunctionComponent() {
return (
+ <h1
+ onClick={() => console.log("onClick FunctionComponent")}
+ onClickCapture={() => console.log("onClickCapture FunctionComponent")}
+ >
+ hello
+ <span
+ style={{ color: "red" }}
+ onClick={() => console.log("onClick span")}
+ onClickCapture={() => console.log("onClickCapture span")}
+ >
+ world
+ </span>
+ </h1>
);
}
let element = <FunctionComponent />;
const root = createRoot(document.getElementById("root"));
root.render(element);
src\react-dom\src\client\ReactDOMRoot.js
import {
createContainer,
updateContainer,
} from "react-reconciler/src/ReactFiberReconciler";
+import { listenToAllSupportedEvents } from "react-dom-bindings/src/events/DOMPluginEventSystem";
function ReactDOMRoot(internalRoot) {
this._internalRoot = internalRoot;
}
ReactDOMRoot.prototype.render = function render(children) {
const root = this._internalRoot;
root.containerInfo.innerHTML = "";
updateContainer(children, root);
};
export function createRoot(container) {
const root = createContainer(container);
+ listenToAllSupportedEvents(container);
return new ReactDOMRoot(root);
}
src\react-dom-bindings\src\events\DOMPluginEventSystem.js
import { allNativeEvents } from "./EventRegistry";
import * as SimpleEventPlugin from "./plugins/SimpleEventPlugin";
SimpleEventPlugin.registerEvents();
export function listenToAllSupportedEvents(rootContainerElement) {
allNativeEvents.forEach((domEventName) => {
console.log(domEventName);
});
}
src\react-dom-bindings\src\events\EventRegistry.js
export const allNativeEvents = new Set();
export function registerTwoPhaseEvent(registrationName, dependencies) {
registerDirectEvent(registrationName, dependencies);
registerDirectEvent(registrationName + "Capture", dependencies);
}
export function registerDirectEvent(registrationName, dependencies) {
for (let i = 0; i < dependencies.length; i++) {
allNativeEvents.add(dependencies[i]); // click
}
}
src\react-dom-bindings\src\events\plugins\SimpleEventPlugin.js
import { registerSimpleEvents } from "../DOMEventProperties";
export { registerSimpleEvents as registerEvents };
src\react-dom-bindings\src\events\DOMEventProperties.js
import { registerTwoPhaseEvent } from "./EventRegistry";
const simpleEventPluginEvents = ["click"];
function registerSimpleEvent(domEventName, reactName) {
registerTwoPhaseEvent(reactName, [domEventName]);
}
export function registerSimpleEvents() {
for (let i = 0; i < simpleEventPluginEvents.length; i++) {
const eventName = simpleEventPluginEvents[i]; // click
const domEventName = eventName.toLowerCase(); // click
const capitalizedEvent = eventName[0].toUpperCase() + eventName.slice(1); // Click
registerSimpleEvent(domEventName, `on${capitalizedEvent}`); // click=>onClick
}
}
src\react-dom-bindings\src\events\DOMPluginEventSystem.js
import { allNativeEvents } from "./EventRegistry";
import * as SimpleEventPlugin from "./plugins/SimpleEventPlugin";
+import { createEventListenerWrapperWithPriority } from "./ReactDOMEventListener";
+import { IS_CAPTURE_PHASE } from "./EventSystemFlags";
+import { addEventCaptureListener, addEventBubbleListener } from "./EventListener";
SimpleEventPlugin.registerEvents();
export function listenToAllSupportedEvents(rootContainerElement) {
allNativeEvents.forEach((domEventName) => {
+ listenToNativeEvent(domEventName, true, rootContainerElement);
+ listenToNativeEvent(domEventName, false, rootContainerElement);
});
}
+export function listenToNativeEvent(domEventName, isCapturePhaseListener, +target) {
+ let eventSystemFlags = 0; // 冒泡 = 0 捕获 = 4
+ if (isCapturePhaseListener) {
+ eventSystemFlags |= IS_CAPTURE_PHASE;
+ }
+ addTrappedEventListener(target, domEventName, eventSystemFlags, isCapturePhaseListener);
+}
+function addTrappedEventListener(targetContainer, domEventName, +eventSystemFlags, isCapturePhaseListener) {
+ const listener = createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags);
+ if (isCapturePhaseListener) {
+ addEventCaptureListener(targetContainer, domEventName, listener);
+ } else {
+ addEventBubbleListener(targetContainer, domEventName, listener);
+ }
+}
src\react-dom-bindings\src\events\EventSystemFlags.js
export const IS_CAPTURE_PHASE = 1 << 2;
src\react-dom-bindings\src\events\ReactDOMEventListener.js
export function createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags) {
const listenerWrapper = dispatchDiscreteEvent;
return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer);
}
function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent) {
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
}
export function dispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) {
console.log("dispatchEvent", domEventName, eventSystemFlags, targetContainer, nativeEvent);
}
src\react-dom-bindings\src\events\EventListener.js
export function addEventCaptureListener(target, eventType, listener) {
target.addEventListener(eventType, listener, true);
return listener;
}
export function addEventBubbleListener(target, eventType, listener) {
target.addEventListener(eventType, listener, false);
return listener;
}
src\react-dom-bindings\src\events\ReactDOMEventListener.js
+import getEventTarget from "./getEventTarget";
+import { getClosestInstanceFromNode } from "../client/ReactDOMComponentTree";
+import { dispatchEventForPluginEventSystem } from "./DOMPluginEventSystem";
+export function createEventListenerWrapperWithPriority(
+ targetContainer,
+ domEventName,
+ eventSystemFlags
+) {
+ const listenerWrapper = dispatchDiscreteEvent;
+ return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer);
+}
+function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent) {
+ dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
+}
export function dispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) {
+ const nativeEventTarget = getEventTarget(nativeEvent);
+ const targetInst = getClosestInstanceFromNode(nativeEventTarget);
+ dispatchEventForPluginEventSystem(
+ domEventName,
+ eventSystemFlags,
+ nativeEvent,
+ targetInst,
+ targetContainer
+ );
}
src\react-dom-bindings\src\events\getEventTarget.js
function getEventTarget(nativeEvent) {
const target = nativeEvent.target || nativeEvent.srcElement || window;
return target;
}
export default getEventTarget;
src\react-dom-bindings\src\client\ReactDOMComponentTree.js
const randomKey = Math.random().toString(36).slice(2);
const internalInstanceKey = "__reactFiber$" + randomKey;
const internalPropsKey = "__reactProps$" + randomKey;
export function getClosestInstanceFromNode(targetNode) {
const targetInst = targetNode[internalInstanceKey];
if (targetInst) {
return targetInst;
}
return null;
}
export function getFiberCurrentPropsFromNode(node) {
return node[internalPropsKey] || null;
}
export function precacheFiberNode(hostInst, node) {
node[internalInstanceKey] = hostInst;
}
export function updateFiberProps(node, props) {
node[internalPropsKey] = props;
}
src\react-dom-bindings\src\events\DOMPluginEventSystem.js
import { allNativeEvents } from "./EventRegistry";
import * as SimpleEventPlugin from "./plugins/SimpleEventPlugin";
import { createEventListenerWrapperWithPriority } from "./ReactDOMEventListener";
import { IS_CAPTURE_PHASE } from "./EventSystemFlags";
import { addEventCaptureListener, addEventBubbleListener } from "./EventListener";
+import getEventTarget from "./getEventTarget";
+import getListener from "./getListener";
+import { HostComponent } from "react-reconciler/src/ReactWorkTags";
SimpleEventPlugin.registerEvents();
export function listenToAllSupportedEvents(rootContainerElement) {
allNativeEvents.forEach((domEventName) => {
listenToNativeEvent(domEventName, true, rootContainerElement);
listenToNativeEvent(domEventName, false, rootContainerElement);
});
}
export function listenToNativeEvent(domEventName, isCapturePhaseListener, target) {
let eventSystemFlags = 0; // 冒泡 = 0 捕获 = 4
if (isCapturePhaseListener) {
eventSystemFlags |= IS_CAPTURE_PHASE;
}
addTrappedEventListener(target, domEventName, eventSystemFlags, isCapturePhaseListener);
}
function addTrappedEventListener(targetContainer, domEventName, eventSystemFlags, isCapturePhaseListener) {
const listener = createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags);
if (isCapturePhaseListener) {
addEventCaptureListener(targetContainer, domEventName, listener);
} else {
addEventBubbleListener(targetContainer, domEventName, listener);
}
}
+export function dispatchEventForPluginEventSystem(
+ domEventName,
+ eventSystemFlags,
+ nativeEvent,
+ targetInst,
+ targetContainer
+) {
+ dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer);
+}
+function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {
+ const nativeEventTarget = getEventTarget(nativeEvent);
+ const dispatchQueue = [];
+ extractEvents(
+ dispatchQueue,
+ domEventName,
+ targetInst,
+ nativeEvent,
+ nativeEventTarget,
+ eventSystemFlags,
+ targetContainer
+ );
+ console.log("dispatchQueue", dispatchQueue);
+}
+function extractEvents(
+ dispatchQueue,
+ domEventName,
+ targetInst,
+ nativeEvent,
+ nativeEventTarget,
+ eventSystemFlags,
+ targetContainer
+) {
+ SimpleEventPlugin.extractEvents(
+ dispatchQueue,
+ domEventName,
+ targetInst,
+ nativeEvent,
+ nativeEventTarget,
+ eventSystemFlags,
+ targetContainer
+ );
+}
+export function accumulateSinglePhaseListeners(targetFiber, reactName, nativeEventType, inCapturePhase) {
+ const captureName = reactName + "Capture";
+ const reactEventName = inCapturePhase ? captureName : reactName;
+ const listeners = [];
+ let instance = targetFiber;
+ while (instance !== null) {
+ const { stateNode, tag } = instance;
+ if (tag === HostComponent && stateNode !== null) {
+ if (reactEventName !== null) {
+ const listener = getListener(instance, reactEventName);
+ if (listener !== null && listener !== undefined) {
+ listeners.push(createDispatchListener(instance, listener, stateNode));
+ }
+ }
+ }
+ instance = instance.return;
+ }
+ return listeners;
+}
+function createDispatchListener(instance, listener, currentTarget) {
+ return {
+ instance,
+ listener,
+ currentTarget,
+ };
+}
src\react-dom-bindings\src\events\getListener.js
import { getFiberCurrentPropsFromNode } from "../client/ReactDOMComponentTree";
export default function getListener(inst, registrationName) {
const stateNode = inst.stateNode;
if (stateNode === null) {
return null;
}
const props = getFiberCurrentPropsFromNode(stateNode);
if (props === null) {
return null;
}
const listener = props[registrationName];
return listener;
}
src\react-dom-bindings\src\events\plugins\SimpleEventPlugin.js
import { registerSimpleEvents, topLevelEventsToReactNames } from "../DOMEventProperties";
import { SyntheticMouseEvent } from "../SyntheticEvent";
import { IS_CAPTURE_PHASE } from "../EventSystemFlags";
import { accumulateSinglePhaseListeners } from "../DOMPluginEventSystem";
+function extractEvents(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags) {
+ const reactName = topLevelEventsToReactNames.get(domEventName);
+ let SyntheticEventCtor;
+ switch (domEventName) {
+ case "click":
+ SyntheticEventCtor = SyntheticMouseEvent;
+ break;
+ default:
+ break;
+ }
+ const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
+ const listeners = accumulateSinglePhaseListeners(targetInst, reactName, nativeEvent.type, inCapturePhase);
+ if (listeners.length > 0) {
+ const event = new SyntheticEventCtor(reactName, domEventName, targetInst, nativeEvent, nativeEventTarget);
+ dispatchQueue.push({
+ event,
+ listeners,
+ });
+ }
+}
+export { registerSimpleEvents as registerEvents, extractEvents };
src\react-dom-bindings\src\events\SyntheticEvent.js
import assign from "shared/assign";
function functionThatReturnsTrue() {
return true;
}
function functionThatReturnsFalse() {
return false;
}
const MouseEventInterface = {
clientX: 0,
clientY: 0,
};
function createSyntheticEvent(Interface) {
function SyntheticBaseEvent(reactName, reactEventType, targetInst, nativeEvent, nativeEventTarget) {
this._reactName = reactName;
this.type = reactEventType;
this._targetInst = targetInst;
this.nativeEvent = nativeEvent;
this.target = nativeEventTarget;
for (const propName in Interface) {
if (!Interface.hasOwnProperty(propName)) {
continue;
}
this[propName] = nativeEvent[propName];
}
this.isDefaultPrevented = functionThatReturnsFalse;
this.isPropagationStopped = functionThatReturnsFalse;
return this;
}
assign(SyntheticBaseEvent.prototype, {
preventDefault() {
const event = this.nativeEvent;
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
this.isDefaultPrevented = functionThatReturnsTrue;
},
stopPropagation() {
const event = this.nativeEvent;
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
this.isPropagationStopped = functionThatReturnsTrue;
},
});
return SyntheticBaseEvent;
}
export const SyntheticMouseEvent = createSyntheticEvent(MouseEventInterface);
src\react-dom-bindings\src\client\ReactDOMHostConfig.js
import { setInitialProperties } from "./ReactDOMComponent";
+import { precacheFiberNode, updateFiberProps } from "./ReactDOMComponentTree";
export function shouldSetTextContent(type, props) {
return typeof props.children === "string" || typeof props.children === "number";
}
export const appendInitialChild = (parent, child) => {
parent.appendChild(child);
};
export const createInstance = (type, props, internalInstanceHandle) => {
const domElement = document.createElement(type);
+ precacheFiberNode(internalInstanceHandle, domElement);
+ updateFiberProps(domElement, props);
return domElement;
};
export const createTextInstance = (content) => document.createTextNode(content);
export function finalizeInitialChildren(domElement, type, props) {
setInitialProperties(domElement, type, props);
}
export function appendChild(parentInstance, child) {
parentInstance.appendChild(child);
}
export function insertBefore(parentInstance, child, beforeChild) {
parentInstance.insertBefore(child, beforeChild);
}
src\react-dom-bindings\src\events\DOMEventProperties.js
import { registerTwoPhaseEvent } from "./EventRegistry";
+export const topLevelEventsToReactNames = new Map();
const simpleEventPluginEvents = ["click"];
function registerSimpleEvent(domEventName, reactName) {
+ topLevelEventsToReactNames.set(domEventName, reactName);
registerTwoPhaseEvent(reactName, [domEventName]);
}
export function registerSimpleEvents() {
for (let i = 0; i < simpleEventPluginEvents.length; i++) {
const eventName = simpleEventPluginEvents[i]; // click
const domEventName = eventName.toLowerCase(); // click
const capitalizedEvent = eventName[0].toUpperCase() + eventName.slice(1); // Click
registerSimpleEvent(domEventName, `on${capitalizedEvent}`); // click=>onClick
}
}
src\react-dom-bindings\src\events\DOMPluginEventSystem.js
import { allNativeEvents } from "./EventRegistry";
import * as SimpleEventPlugin from "./plugins/SimpleEventPlugin";
import { createEventListenerWrapperWithPriority } from "./ReactDOMEventListener";
import { IS_CAPTURE_PHASE } from "./EventSystemFlags";
import { addEventCaptureListener, addEventBubbleListener } from "./EventListener";
import getEventTarget from "./getEventTarget";
import getListener from "./getListener";
import { HostComponent } from "react-reconciler/src/ReactWorkTags";
SimpleEventPlugin.registerEvents();
export function listenToAllSupportedEvents(rootContainerElement) {
allNativeEvents.forEach((domEventName) => {
listenToNativeEvent(domEventName, true, rootContainerElement);
listenToNativeEvent(domEventName, false, rootContainerElement);
});
}
export function listenToNativeEvent(domEventName, isCapturePhaseListener, target) {
let eventSystemFlags = 0; // 冒泡 = 0 捕获 = 4
if (isCapturePhaseListener) {
eventSystemFlags |= IS_CAPTURE_PHASE;
}
addTrappedEventListener(target, domEventName, eventSystemFlags, isCapturePhaseListener);
}
function addTrappedEventListener(targetContainer, domEventName, eventSystemFlags, isCapturePhaseListener) {
const listener = createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags);
if (isCapturePhaseListener) {
addEventCaptureListener(targetContainer, domEventName, listener);
} else {
addEventBubbleListener(targetContainer, domEventName, listener);
}
}
export function dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer
) {
dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer);
}
function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {
const nativeEventTarget = getEventTarget(nativeEvent);
const dispatchQueue = [];
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer
);
+ processDispatchQueue(dispatchQueue, eventSystemFlags);
}
+export function processDispatchQueue(dispatchQueue, eventSystemFlags) {
+ const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
+ for (let i = 0; i < dispatchQueue.length; i++) {
+ const { event, listeners } = dispatchQueue[i];
+ processDispatchQueueItemsInOrder(event, listeners, inCapturePhase); // event system doesn't use pooling.
+ }
+}
+function processDispatchQueueItemsInOrder(event, dispatchListeners, inCapturePhase) {
+ if (inCapturePhase) {
+ for (let i = dispatchListeners.length - 1; i >= 0; i--) {
+ const { currentTarget, listener } = dispatchListeners[i];
+ if (event.isPropagationStopped()) {
+ return;
+ }
+ executeDispatch(event, listener, currentTarget);
+ }
+ } else {
+ for (let i = 0; i < dispatchListeners.length; i++) {
+ const { currentTarget, listener } = dispatchListeners[i];
+ if (event.isPropagationStopped()) {
+ return;
+ }
+ executeDispatch(event, listener, currentTarget);
+ }
+ }
+}
+function executeDispatch(event, listener, currentTarget) {
+ event.currentTarget = currentTarget;
+ listener(event);
+ event.currentTarget = null;
+}
function extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer
) {
SimpleEventPlugin.extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer
);
}
export function accumulateSinglePhaseListeners(targetFiber, reactName, nativeEventType, inCapturePhase) {
const captureName = reactName + "Capture";
const reactEventName = inCapturePhase ? captureName : reactName;
const listeners = [];
let instance = targetFiber;
while (instance !== null) {
const { stateNode, tag } = instance;
if (tag === HostComponent && stateNode !== null) {
if (reactEventName !== null) {
const listener = getListener(instance, reactEventName);
if (listener !== null && listener !== undefined) {
listeners.push(createDispatchListener(instance, listener, stateNode));
}
}
}
instance = instance.return;
}
return listeners;
}
function createDispatchListener(instance, listener, currentTarget) {
return {
instance,
listener,
currentTarget,
};
}
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
+const reducer = (state, action) => {
+ if (action.type === "add") return state + 1;
+ return state;
+};
function FunctionComponent() {
+ const [number, setNumber] = React.useReducer(reducer, 0);
+ return <button onClick={() => setNumber({ type: "add" })}>{number}</button>;
}
let element = <FunctionComponent />;
const root = createRoot(document.getElementById("root"));
root.render(element);
src\react-reconciler\src\ReactFiberHooks.js
+import ReactSharedInternals from "shared/ReactSharedInternals";
+
+const { ReactCurrentDispatcher } = ReactSharedInternals;
+let currentlyRenderingFiber = null;
+let workInProgressHook = null;
+
+function mountWorkInProgressHook() {
+ const hook = {
+ memoizedState: null,
+ queue: null,
+ next: null,
+ };
+ if (workInProgressHook === null) {
+ currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
+ } else {
+ workInProgressHook = workInProgressHook.next = hook;
+ }
+ return workInProgressHook;
+}
+function dispatchReducerAction(fiber, queue, action) {
+ console.log("dispatchReducerAction", action);
+}
+const HooksDispatcherOnMountInDEV = {
+ useReducer:mountReducer
+};
+function useReducer(reducer, initialArg){
+ const hook = mountWorkInProgressHook();
+ hook.memoizedState = initialArg;
+ const queue = {
+ pending: null,
+ dispatch: null,
+ };
+ hook.queue = queue;
+ const dispatch = (queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber, queue));
+ return [hook.memoizedState, dispatch];
}
export function renderWithHooks(current, workInProgress, Component, props) {
+ currentlyRenderingFiber = workInProgress;
+ if (current !== null && current.memoizedState !== null) {
+ } else {
+ ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
+ }
const children = Component(props);
+ currentlyRenderingFiber = null;
return children;
}
src\react\index.js
export { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, useReducer } from "./src/React";
src\react\src\React.js
import { useReducer } from "./ReactHooks";
import ReactSharedInternals from "./ReactSharedInternals";
export { useReducer, ReactSharedInternals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED };
src\react\src\ReactHooks.js
import ReactCurrentDispatcher from "./ReactCurrentDispatcher";
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return dispatcher;
}
export function useReducer(reducer, initialArg, init) {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg, init);
}
src\react\src\ReactCurrentDispatcher.js
const ReactCurrentDispatcher = {
current: null,
};
export default ReactCurrentDispatcher;
src\react\src\ReactSharedInternals.js
import ReactCurrentDispatcher from "./ReactCurrentDispatcher";
const ReactSharedInternals = {
ReactCurrentDispatcher,
};
export default ReactSharedInternals;
src\shared\ReactSharedInternals.js
import * as React from "react";
const ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
export default ReactSharedInternals;
src\react-reconciler\src\ReactFiberHooks.js
import ReactSharedInternals from "shared/ReactSharedInternals";
+import { enqueueConcurrentHookUpdate } from "./ReactFiberConcurrentUpdates";
+import { scheduleUpdateOnFiber } from "./ReactFiberWorkLoop";
const { ReactCurrentDispatcher } = ReactSharedInternals;
let currentlyRenderingFiber = null;
let workInProgressHook = null;
+let currentHook = null;
function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function dispatchReducerAction(fiber, queue, action) {
+ const update = {
+ action,
+ next: null,
+ };
+ const root = enqueueConcurrentHookUpdate(fiber, queue, update);
+ scheduleUpdateOnFiber(root, fiber);
}
const HooksDispatcherOnMountInDEV = {
useReducer:mountReducer
};
function mountReducer(reducer, initialArg) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialArg;
const queue = {
pending: null,
dispatch: null,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
+function updateWorkInProgressHook() {
+ if (currentHook === null) {
+ const current = currentlyRenderingFiber.alternate
+ currentHook = current.memoizedState
+ } else {
+ currentHook = currentHook.next
+ }
+ const newHook = {
+ memoizedState: currentHook.memoizedState,
+ queue: currentHook.queue,
+ next: null
+ }
+ if (workInProgressHook === null) {
+ currentlyRenderingFiber.memoizedState = workInProgressHook = newHook
+ } else {
+ workInProgressHook = workInProgressHook.next = newHook
+ }
+ return workInProgressHook
+}
+const HooksDispatcherOnUpdateInDEV = {
+ useReducer:updateReducer
+};
+function updateReducer(reducer) {
+ const hook = updateWorkInProgressHook()
+ const queue = hook.queue
+ queue.lastRenderedReducer = reducer
+ const current = currentHook
+ const pendingQueue = queue.pending
+ let newState = current.memoizedState
+ if (pendingQueue !== null) {
+ queue.pending = null
+ const first = pendingQueue.next
+ let update = first
+ do {
+ if (update.hasEagerState) {
+ newState = update.eagerState
+ } else {
+ const action = update.action
+ newState = reducer(newState, action)
+ }
+ update = update.next
+ } while (update !== null && update !== first)
+ }
+ hook.memoizedState = queue.lastRenderedState = newState
+ return [hook.memoizedState, queue.dispatch]
+},
export function renderWithHooks(current, workInProgress, Component, props) {
currentlyRenderingFiber = workInProgress;
if (current !== null && current.memoizedState !== null) {
+ ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
const children = Component(props);
currentlyRenderingFiber = null;
+ workInProgressHook = null;
+ currentHook = null;
return children;
}
src\react-reconciler\src\ReactFiberConcurrentUpdates.js
import { HostRoot } from "./ReactWorkTags";
+const concurrentQueues = [];
+let concurrentQueuesIndex = 0;
export function markUpdateLaneFromFiberToRoot(sourceFiber) {
let node = sourceFiber;
let parent = sourceFiber.return;
while (parent !== null) {
node = parent;
parent = parent.return;
}
if (node.tag === HostRoot) {
const root = node.stateNode;
return root;
}
return null;
}
+export function enqueueConcurrentHookUpdate(fiber, queue, update) {
+ enqueueUpdate(fiber, queue, update);
+ return getRootForUpdatedFiber(fiber);
+}
+function enqueueUpdate(fiber, queue, update) {
+ concurrentQueues[concurrentQueuesIndex++] = fiber;
+ concurrentQueues[concurrentQueuesIndex++] = queue;
+ concurrentQueues[concurrentQueuesIndex++] = update;
+}
+function getRootForUpdatedFiber(sourceFiber) {
+ let node = sourceFiber;
+ let parent = node.return;
+ while (parent !== null) {
+ node = parent;
+ parent = node.return;
+ }
+ return node.tag === HostRoot ? node.stateNode : null;
+}
+export function finishQueueingConcurrentUpdates() {
+ const endIndex = concurrentQueuesIndex;
+ concurrentQueuesIndex = 0;
+ let i = 0;
+ while (i < endIndex) {
+ const fiber = concurrentQueues[i++];
+ const queue = concurrentQueues[i++];
+ const update = concurrentQueues[i++];
+ if (queue !== null && update !== null) {
+ const pending = queue.pending;
+ if (pending === null) {
+ update.next = update;
+ } else {
+ update.next = pending.next;
+ pending.next = update;
+ }
+ queue.pending = update;
+ }
+ }
+}
src\react-reconciler\src\ReactFiberWorkLoop.js
import { scheduleCallback } from "scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
import { completeWork } from "./ReactFiberCompleteWork";
import { MutationMask, NoFlags } from "./ReactFiberFlags";
import { commitMutationEffectsOnFiber } from "./ReactFiberCommitWork";
+import { finishQueueingConcurrentUpdates } from "./ReactFiberConcurrentUpdates";
let workInProgress = null;
export function scheduleUpdateOnFiber(root) {
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}
function performConcurrentWorkOnRoot(root) {
renderRootSync(root);
const finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
commitRoot(root);
}
function commitRoot(root) {
const { finishedWork } = root;
const subtreeHasEffects = (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
commitMutationEffectsOnFiber(finishedWork, root);
}
root.current = finishedWork;
}
function prepareFreshStack(root) {
workInProgress = createWorkInProgress(root.current, null);
+ finishQueueingConcurrentUpdates();
}
function renderRootSync(root) {
prepareFreshStack(root);
workLoopSync();
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
completeWork(current, completedWork);
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
}
src\react-reconciler\src\ReactFiberBeginWork.js
import { HostRoot, HostComponent, HostText, IndeterminateComponent, FunctionComponent } from "./ReactWorkTags";
import { processUpdateQueue } from "./ReactFiberClassUpdateQueue";
import { mountChildFibers, reconcileChildFibers } from "./ReactChildFiber";
import { shouldSetTextContent } from "react-dom-bindings/src/client/ReactDOMHostConfig";
import logger, { indent } from "shared/logger";
import { renderWithHooks } from "react-reconciler/src/ReactFiberHooks";
function reconcileChildren(current, workInProgress, nextChildren) {
if (current === null) {
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren);
} else {
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren);
}
}
function updateHostRoot(current, workInProgress) {
processUpdateQueue(workInProgress);
const nextState = workInProgress.memoizedState;
const nextChildren = nextState.element;
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
function updateHostComponent(current, workInProgress) {
const { type } = workInProgress;
const nextProps = workInProgress.pendingProps;
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
nextChildren = null;
}
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
function mountIndeterminateComponent(_current, workInProgress, Component) {
const props = workInProgress.pendingProps;
const value = renderWithHooks(null, workInProgress, Component, props);
workInProgress.tag = FunctionComponent;
reconcileChildren(null, workInProgress, value);
return workInProgress.child;
}
+function updateFunctionComponent(current, workInProgress, Component, nextProps) {
+ const nextChildren = renderWithHooks(current, workInProgress, Component, nextProps);
+ reconcileChildren(current, workInProgress, nextChildren);
+ return workInProgress.child;
+}
export function beginWork(current, workInProgress) {
//logger(" ".repeat(indent.number) + "beginWork", workInProgress);
indent.number += 2;
switch (workInProgress.tag) {
case IndeterminateComponent: {
return mountIndeterminateComponent(current, workInProgress, workInProgress.type);
}
+ case FunctionComponent: {
+ const Component = workInProgress.type;
+ const resolvedProps = workInProgress.pendingProps;
+ return updateFunctionComponent(current, workInProgress, Component, resolvedProps);
+ }
case HostRoot:
return updateHostRoot(current, workInProgress);
case HostComponent:
return updateHostComponent(current, workInProgress);
case HostText:
default:
return null;
}
}
src\react-reconciler\src\ReactChildFiber.js
import { REACT_ELEMENT_TYPE } from "shared/ReactSymbols";
import isArray from "shared/isArray";
import { createFiberFromElement, FiberNode, createFiberFromText, createWorkInProgress } from "./ReactFiber";
import { Placement } from "./ReactFiberFlags";
import { HostText } from "./ReactWorkTags";
function createChildReconciler(shouldTrackSideEffects) {
+ function useFiber(fiber, pendingProps) {
+ const clone = createWorkInProgress(fiber, pendingProps);
+ clone.index = 0;
+ clone.sibling = null;
+ return clone;
+ }
function reconcileSingleElement(returnFiber, currentFirstChild, element) {
+ const key = element.key;
+ let child = currentFirstChild;
+ while (child !== null) {
+ if (child.key === key) {
+ const elementType = element.type;
+ if (child.type === elementType) {
+ const existing = useFiber(child, element.props);
+ existing.return = returnFiber;
+ return existing;
+ }
+ }
+ child = child.sibling;
+ }
const created = createFiberFromElement(element);
created.return = returnFiber;
return created;
}
function placeSingleChild(newFiber) {
+ if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags |= Placement;
}
return newFiber;
}
function reconcileSingleTextNode(returnFiber, currentFirstChild, content) {
const created = new FiberNode(HostText, { content }, null);
created.return = returnFiber;
return created;
}
function createChild(returnFiber, newChild) {
if ((typeof newChild === "string" && newChild !== "") || typeof newChild === "number") {
const created = createFiberFromText(`${newChild}`);
created.return = returnFiber;
return created;
}
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(newChild);
created.return = returnFiber;
return created;
}
default:
break;
}
}
return null;
}
function placeChild(newFiber, newIndex) {
newFiber.index = newIndex;
if (shouldTrackSideEffects) newFiber.flags |= Placement;
}
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) {
let resultingFirstChild = null;
let previousNewFiber = null;
let newIdx = 0;
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx]);
if (newFiber === null) {
continue;
}
placeChild(newFiber, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
function reconcileChildFibers(returnFiber, currentFirstChild, newChild) {
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild));
}
default:
break;
}
if (isArray(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild);
}
}
if (typeof newChild === "string") {
return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, newChild));
}
return null;
}
return reconcileChildFibers;
}
export const reconcileChildFibers = createChildReconciler(true);
export const mountChildFibers = createChildReconciler(false);
src\react-reconciler\src\ReactFiberCompleteWork.js
import {
appendInitialChild,
createInstance,
createTextInstance,
finalizeInitialChildren,
+ prepareUpdate,
} from "react-dom-bindings/src/client/ReactDOMHostConfig";
import { HostComponent, HostRoot, HostText } from "./ReactWorkTags";
+import { NoFlags, Update } from "./ReactFiberFlags";
import logger, { indent } from "shared/logger";
function bubbleProperties(completedWork) {
let subtreeFlags = NoFlags;
let child = completedWork.child;
while (child !== null) {
subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;
child = child.sibling;
}
completedWork.subtreeFlags |= subtreeFlags;
}
function appendAllChildren(parent, workInProgress) {
// 我们只有创建的顶级fiber,但需要递归其子节点来查找所有终端节点
let node = workInProgress.child;
while (node !== null) {
// 如果是原生节点,直接添加到父节点上
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
// 再看看第一个节节点是不是原生节点
} else if (node.child !== null) {
// node.child.return = node
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
// 如果没有弟弟就找父亲的弟弟
while (node.sibling === null) {
// 如果找到了根节点或者回到了原节点结束
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
// node.sibling.return = node.return
// 下一个弟弟节点
node = node.sibling;
}
}
+function markUpdate(workInProgress) {
+ workInProgress.flags |= Update;
+}
+function updateHostComponent(current, workInProgress, type, newProps) {
+ const oldProps = current.memoizedProps;
+ const instance = workInProgress.stateNode;
+ const updatePayload = prepareUpdate(instance, type, oldProps, newProps);
+ workInProgress.updateQueue = updatePayload;
+ if (updatePayload) {
+ markUpdate(workInProgress);
+ }
+}
export function completeWork(current, workInProgress) {
indent.number -= 2;
//logger(" ".repeat(indent.number) + "completeWork", workInProgress);
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent: {
const { type } = workInProgress;
+ if (current !== null && workInProgress.stateNode != null) {
+ updateHostComponent(current, workInProgress, type, newProps);
+ console.log("updatePayload", workInProgress.updateQueue);
+ } else {
const instance = createInstance(type, newProps, workInProgress);
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;
finalizeInitialChildren(instance, type, newProps);
+ }
+ bubbleProperties(workInProgress);
break;
}
case HostRoot:
bubbleProperties(workInProgress);
break;
case HostText: {
const newText = newProps;
workInProgress.stateNode = createTextInstance(newText);
bubbleProperties(workInProgress);
break;
}
default:
break;
}
}
src\react-reconciler\src\ReactFiberFlags.js
export const NoFlags = 0b00000000000000000000000000;
export const Placement = 0b00000000000000000000000010;
+export const Update = 0b00000000000000000000000100;
+export const MutationMask = Placement | Update;
src\react-dom-bindings\src\client\ReactDOMHostConfig.js
+import { setInitialProperties, diffProperties } from "./ReactDOMComponent";
import { precacheFiberNode, updateFiberProps } from "./ReactDOMComponentTree";
export function shouldSetTextContent(type, props) {
return typeof props.children === "string" || typeof props.children === "number";
}
export const appendInitialChild = (parent, child) => {
parent.appendChild(child);
};
export const createInstance = (type, props, internalInstanceHandle) => {
const domElement = document.createElement(type);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
};
export const createTextInstance = (content) => document.createTextNode(content);
export function finalizeInitialChildren(domElement, type, props) {
setInitialProperties(domElement, type, props);
}
export function appendChild(parentInstance, child) {
parentInstance.appendChild(child);
}
export function insertBefore(parentInstance, child, beforeChild) {
parentInstance.insertBefore(child, beforeChild);
}
+export function prepareUpdate(domElement, type, oldProps, newProps) {
+ return diffProperties(domElement, type, oldProps, newProps);
+}
src\react-dom-bindings\src\client\ReactDOMComponent.js
import { setValueForStyles } from "./CSSPropertyOperations";
import setTextContent from "./setTextContent";
import { setValueForProperty } from "./DOMPropertyOperations";
const CHILDREN = "children";
const STYLE = "style";
function setInitialDOMProperties(tag, domElement, nextProps) {
for (const propKey in nextProps) {
if (nextProps.hasOwnProperty(propKey)) {
const nextProp = nextProps[propKey];
if (propKey === STYLE) {
setValueForStyles(domElement, nextProp);
} else if (propKey === CHILDREN) {
if (typeof nextProp === "string") {
setTextContent(domElement, nextProp);
} else if (typeof nextProp === "number") {
setTextContent(domElement, `${nextProp}`);
}
} else if (nextProp != null) {
setValueForProperty(domElement, propKey, nextProp);
}
}
}
}
export function setInitialProperties(domElement, tag, props) {
setInitialDOMProperties(tag, domElement, props);
}
+export function diffProperties(domElement, tag, lastProps, nextProps) {
+ let updatePayload = null;
+ let propKey;
+ let styleName;
+ let styleUpdates = null;
+ for (propKey in lastProps) {
+ if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] == null) {
+ continue;
+ }
+ if (propKey === STYLE) {
+ const lastStyle = lastProps[propKey];
+ for (styleName in lastStyle) {
+ if (lastStyle.hasOwnProperty(styleName)) {
+ if (!styleUpdates) {
+ styleUpdates = {};
+ }
+ styleUpdates[styleName] = "";
+ }
+ }
+ } else {
+ (updatePayload = updatePayload || []).push(propKey, null);
+ }
+ }
+ for (propKey in nextProps) {
+ const nextProp = nextProps[propKey];
+ const lastProp = lastProps != null ? lastProps[propKey] : undefined;
+ if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || (nextProp == null && lastProp == null)) {
+ continue;
+ }
+ if (propKey === STYLE) {
+ if (lastProp) {
+ for (styleName in lastProp) {
+ if (lastProp.hasOwnProperty(styleName) && (!nextProp || !nextProp.hasOwnProperty(styleName))) {
+ if (!styleUpdates) {
+ styleUpdates = {};
+ }
+ styleUpdates[styleName] = "";
+ }
+ }
+ for (styleName in nextProp) {
+ if (nextProp.hasOwnProperty(styleName) && lastProp[styleName] !== nextProp[styleName]) {
+ if (!styleUpdates) {
+ styleUpdates = {};
+ }
+ styleUpdates[styleName] = nextProp[styleName];
+ }
+ }
+ } else {
+ if (!styleUpdates) {
+ if (!updatePayload) {
+ updatePayload = [];
+ }
+ updatePayload.push(propKey, styleUpdates);
+ }
+ styleUpdates = nextProp;
+ }
+ } else if (propKey === CHILDREN) {
+ if (typeof nextProp === "string" || typeof nextProp === "number") {
+ (updatePayload = updatePayload || []).push(propKey, "" + nextProp);
+ }
+ } else {
+ (updatePayload = updatePayload || []).push(propKey, nextProp);
+ }
+ }
+ if (styleUpdates) {
+ (updatePayload = updatePayload || []).push(STYLE, styleUpdates);
+ }
+ return updatePayload;
+}
src\react-dom-bindings\src\events\DOMPluginEventSystem.js
import { allNativeEvents } from "./EventRegistry";
import * as SimpleEventPlugin from "./plugins/SimpleEventPlugin";
import { createEventListenerWrapperWithPriority } from "./ReactDOMEventListener";
import { IS_CAPTURE_PHASE } from "./EventSystemFlags";
import { addEventCaptureListener, addEventBubbleListener } from "./EventListener";
import getEventTarget from "./getEventTarget";
import getListener from "./getListener";
import { HostComponent } from "react-reconciler/src/ReactWorkTags";
SimpleEventPlugin.registerEvents();
+const listeningMarker = "_reactListening" + Math.random().toString(36).slice(2);
export function listenToAllSupportedEvents(rootContainerElement) {
+ if (!rootContainerElement[listeningMarker]) {
+ rootContainerElement[listeningMarker] = true;
allNativeEvents.forEach((domEventName) => {
listenToNativeEvent(domEventName, true, rootContainerElement);
listenToNativeEvent(domEventName, false, rootContainerElement);
});
+ }
}
export function listenToNativeEvent(domEventName, isCapturePhaseListener, target) {
let eventSystemFlags = 0; // 冒泡 = 0 捕获 = 4
if (isCapturePhaseListener) {
eventSystemFlags |= IS_CAPTURE_PHASE;
}
addTrappedEventListener(target, domEventName, eventSystemFlags, isCapturePhaseListener);
}
function addTrappedEventListener(targetContainer, domEventName, eventSystemFlags, isCapturePhaseListener) {
const listener = createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags);
if (isCapturePhaseListener) {
addEventCaptureListener(targetContainer, domEventName, listener);
} else {
addEventBubbleListener(targetContainer, domEventName, listener);
}
}
export function dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer
) {
dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer);
}
function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {
const nativeEventTarget = getEventTarget(nativeEvent);
const dispatchQueue = [];
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer
);
processDispatchQueue(dispatchQueue, eventSystemFlags);
}
export function processDispatchQueue(dispatchQueue, eventSystemFlags) {
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
for (let i = 0; i < dispatchQueue.length; i++) {
const { event, listeners } = dispatchQueue[i];
processDispatchQueueItemsInOrder(event, listeners, inCapturePhase); // event system doesn't use pooling.
}
}
function processDispatchQueueItemsInOrder(event, dispatchListeners, inCapturePhase) {
if (inCapturePhase) {
for (let i = dispatchListeners.length - 1; i >= 0; i--) {
const { currentTarget, listener } = dispatchListeners[i];
if (event.isPropagationStopped()) {
return;
}
executeDispatch(event, listener, currentTarget);
}
} else {
for (let i = 0; i < dispatchListeners.length; i++) {
const { currentTarget, listener } = dispatchListeners[i];
if (event.isPropagationStopped()) {
return;
}
executeDispatch(event, listener, currentTarget);
}
}
}
function executeDispatch(event, listener, currentTarget) {
event.currentTarget = currentTarget;
listener(event);
event.currentTarget = null;
}
function extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer
) {
SimpleEventPlugin.extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer
);
}
export function accumulateSinglePhaseListeners(targetFiber, reactName, nativeEventType, inCapturePhase) {
const captureName = reactName + "Capture";
const reactEventName = inCapturePhase ? captureName : reactName;
const listeners = [];
let instance = targetFiber;
while (instance !== null) {
const { stateNode, tag } = instance;
if (tag === HostComponent && stateNode !== null) {
if (reactEventName !== null) {
const listener = getListener(instance, reactEventName);
if (listener !== null && listener !== undefined) {
listeners.push(createDispatchListener(instance, listener, stateNode));
}
}
}
instance = instance.return;
}
return listeners;
}
function createDispatchListener(instance, listener, currentTarget) {
return {
instance,
listener,
currentTarget,
};
}
src\react-reconciler\src\ReactFiberCompleteWork.js
import {
appendInitialChild,
createInstance,
createTextInstance,
finalizeInitialChildren,
prepareUpdate,
} from "react-dom-bindings/src/client/ReactDOMHostConfig";
+import { HostComponent, HostRoot, HostText, FunctionComponent } from "./ReactWorkTags";
import { NoFlags, Update } from "./ReactFiberFlags";
import logger, { indent } from "shared/logger";
function bubbleProperties(completedWork) {
let subtreeFlags = NoFlags;
let child = completedWork.child;
while (child !== null) {
subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;
child = child.sibling;
}
completedWork.subtreeFlags |= subtreeFlags;
}
function appendAllChildren(parent, workInProgress) {
// 我们只有创建的顶级fiber,但需要递归其子节点来查找所有终端节点
let node = workInProgress.child;
while (node !== null) {
// 如果是原生节点,直接添加到父节点上
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
// 再看看第一个节节点是不是原生节点
} else if (node.child !== null) {
// node.child.return = node
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
// 如果没有弟弟就找父亲的弟弟
while (node.sibling === null) {
// 如果找到了根节点或者回到了原节点结束
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
// node.sibling.return = node.return
// 下一个弟弟节点
node = node.sibling;
}
}
function markUpdate(workInProgress) {
workInProgress.flags |= Update;
}
function updateHostComponent(current, workInProgress, type, newProps) {
const oldProps = current.memoizedProps;
const instance = workInProgress.stateNode;
const updatePayload = prepareUpdate(instance, type, oldProps, newProps);
workInProgress.updateQueue = updatePayload;
if (updatePayload) {
markUpdate(workInProgress);
}
}
export function completeWork(current, workInProgress) {
indent.number -= 2;
//logger(" ".repeat(indent.number) + "completeWork", workInProgress);
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent: {
const { type } = workInProgress;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, type, newProps);
} else {
const instance = createInstance(type, newProps, workInProgress);
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;
finalizeInitialChildren(instance, type, newProps);
}
bubbleProperties(workInProgress);
break;
}
+ case FunctionComponent:
+ bubbleProperties(workInProgress);
+ break;
case HostRoot:
bubbleProperties(workInProgress);
break;
case HostText: {
const newText = newProps;
workInProgress.stateNode = createTextInstance(newText);
bubbleProperties(workInProgress);
break;
}
default:
break;
}
}
src\react-reconciler\src\ReactFiberCommitWork.js
+import { HostRoot, HostComponent, HostText, FunctionComponent } from "./ReactWorkTags";
+import { MutationMask, Placement, Update } from "./ReactFiberFlags";
+import { insertBefore, appendChild, commitUpdate } from "react-dom-bindings/src/client/ReactDOMHostConfig";
function recursivelyTraverseMutationEffects(root, parentFiber) {
if (parentFiber.subtreeFlags & MutationMask) {
let { child } = parentFiber;
while (child !== null) {
commitMutationEffectsOnFiber(child, root);
child = child.sibling;
}
}
}
function isHostParent(fiber) {
return fiber.tag === HostComponent || fiber.tag === HostRoot;
}
function getHostParentFiber(fiber) {
let parent = fiber.return;
while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}
parent = parent.return;
}
return parent;
}
function insertOrAppendPlacementNode(node, before, parent) {
const { tag } = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost) {
const { stateNode } = node;
if (before) {
insertBefore(parent, stateNode, before);
} else {
appendChild(parent, stateNode);
}
} else {
const { child } = node;
if (child !== null) {
insertOrAppendPlacementNode(child, before, parent);
let { sibling } = child;
while (sibling !== null) {
insertOrAppendPlacementNode(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}
function getHostSibling(fiber) {
let node = fiber;
siblings: while (true) {
// 如果我们没有找到任何东西,让我们试试下一个弟弟
while (node.sibling === null) {
if (node.return === null || isHostParent(node.return)) {
// 如果我们是根Fiber或者父亲是原生节点,我们就是最后的弟弟
return null;
}
node = node.return;
}
// node.sibling.return = node.return
node = node.sibling;
while (node.tag !== HostComponent && node.tag !== HostText) {
// 如果它不是原生节点,并且,我们可能在其中有一个原生节点
// 试着向下搜索,直到找到为止
if (node.flags & Placement) {
// 如果我们没有孩子,可以试试弟弟
continue siblings;
} else {
// node.child.return = node
node = node.child;
}
} // Check if this host node is stable or about to be placed.
// 检查此原生节点是否稳定可以放置
if (!(node.flags & Placement)) {
// 找到它了!
return node.stateNode;
}
}
}
function commitPlacement(finishedWork) {
const parentFiber = getHostParentFiber(finishedWork);
switch (parentFiber.tag) {
case HostComponent: {
const parent = parentFiber.stateNode;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
case HostRoot: {
const parent = parentFiber.stateNode.containerInfo;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
default:
break;
}
}
function commitReconciliationEffects(finishedWork) {
const { flags } = finishedWork;
if (flags & Placement) {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}
}
export function commitMutationEffectsOnFiber(finishedWork, root) {
+ const current = finishedWork.alternate;
+ const flags = finishedWork.flags;
switch (finishedWork.tag) {
+ case HostRoot: {
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ commitReconciliationEffects(finishedWork);
+ break;
+ }
+ case FunctionComponent: {
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ commitReconciliationEffects(finishedWork);
+ break;
+ }
+ case HostComponent: {
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ commitReconciliationEffects(finishedWork);
+ if (flags & Update) {
+ const instance = finishedWork.stateNode;
+ if (instance != null) {
+ const newProps = finishedWork.memoizedProps;
+ const oldProps = current !== null ? current.memoizedProps : newProps;
+ const type = finishedWork.type;
+ const updatePayload = finishedWork.updateQueue;
+ finishedWork.updateQueue = null;
+ if (updatePayload !== null) {
+ commitUpdate(instance, updatePayload, type, oldProps, newProps, finishedWork);
+ }
+ }
+ }
+ break;
+ }
+ case HostText: {
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ commitReconciliationEffects(finishedWork);
+ break;
+ }
default: {
break;
}
}
}
src\react-dom-bindings\src\client\ReactDOMHostConfig.js
+import { setInitialProperties, diffProperties, updateProperties } from "./ReactDOMComponent";
import { precacheFiberNode, updateFiberProps } from "./ReactDOMComponentTree";
export function shouldSetTextContent(type, props) {
return typeof props.children === "string" || typeof props.children === "number";
}
export const appendInitialChild = (parent, child) => {
parent.appendChild(child);
};
export const createInstance = (type, props, internalInstanceHandle) => {
const domElement = document.createElement(type);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
};
export const createTextInstance = (content) => document.createTextNode(content);
export function finalizeInitialChildren(domElement, type, props) {
setInitialProperties(domElement, type, props);
}
export function appendChild(parentInstance, child) {
parentInstance.appendChild(child);
}
export function insertBefore(parentInstance, child, beforeChild) {
parentInstance.insertBefore(child, beforeChild);
}
export function prepareUpdate(domElement, type, oldProps, newProps) {
return diffProperties(domElement, type, oldProps, newProps);
}
+export function commitUpdate(domElement, updatePayload, type, oldProps, newProps) {
+ updateProperties(domElement, updatePayload, type, oldProps, newProps);
+ updateFiberProps(domElement, newProps);
+}
src\react-dom-bindings\src\client\ReactDOMComponent.js
import { setValueForStyles } from "./CSSPropertyOperations";
import setTextContent from "./setTextContent";
import { setValueForProperty } from "./DOMPropertyOperations";
const CHILDREN = "children";
const STYLE = "style";
function setInitialDOMProperties(tag, domElement, nextProps) {
for (const propKey in nextProps) {
if (nextProps.hasOwnProperty(propKey)) {
const nextProp = nextProps[propKey];
if (propKey === STYLE) {
setValueForStyles(domElement, nextProp);
} else if (propKey === CHILDREN) {
if (typeof nextProp === "string") {
setTextContent(domElement, nextProp);
} else if (typeof nextProp === "number") {
setTextContent(domElement, `${nextProp}`);
}
} else if (nextProp != null) {
setValueForProperty(domElement, propKey, nextProp);
}
}
}
}
export function setInitialProperties(domElement, tag, props) {
setInitialDOMProperties(tag, domElement, props);
}
export function diffProperties(domElement, tag, lastProps, nextProps) {
let updatePayload = null;
let propKey;
let styleName;
let styleUpdates = null;
for (propKey in lastProps) {
if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] == null) {
continue;
}
if (propKey === STYLE) {
const lastStyle = lastProps[propKey];
for (styleName in lastStyle) {
if (lastStyle.hasOwnProperty(styleName)) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = "";
}
}
} else {
(updatePayload = updatePayload || []).push(propKey, null);
}
}
for (propKey in nextProps) {
const nextProp = nextProps[propKey];
const lastProp = lastProps != null ? lastProps[propKey] : undefined;
if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || (nextProp == null && lastProp == null)) {
continue;
}
if (propKey === STYLE) {
if (lastProp) {
for (styleName in lastProp) {
if (lastProp.hasOwnProperty(styleName) && (!nextProp || !nextProp.hasOwnProperty(styleName))) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = "";
}
}
for (styleName in nextProp) {
if (nextProp.hasOwnProperty(styleName) && lastProp[styleName] !== nextProp[styleName]) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = nextProp[styleName];
}
}
} else {
if (!styleUpdates) {
if (!updatePayload) {
updatePayload = [];
}
updatePayload.push(propKey, styleUpdates);
}
styleUpdates = nextProp;
}
} else if (propKey === CHILDREN) {
if (typeof nextProp === "string" || typeof nextProp === "number") {
(updatePayload = updatePayload || []).push(propKey, "" + nextProp);
}
} else {
(updatePayload = updatePayload || []).push(propKey, nextProp);
}
}
if (styleUpdates) {
(updatePayload = updatePayload || []).push(STYLE, styleUpdates);
}
return updatePayload;
}
+export function updateProperties(domElement, updatePayload) {
+ updateDOMProperties(domElement, updatePayload);
+}
+
+function updateDOMProperties(domElement, updatePayload) {
+ for (let i = 0; i < updatePayload.length; i += 2) {
+ const propKey = updatePayload[i];
+ const propValue = updatePayload[i + 1];
+ if (propKey === STYLE) {
+ setValueForStyles(domElement, propValue);
+ } else if (propKey === CHILDREN) {
+ setTextContent(domElement, propValue);
+ } else {
+ setValueForProperty(domElement, propKey, propValue);
+ }
+ }
+}
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
function FunctionComponent() {
console.log("FunctionComponent render");
+ const [number, setNumber] = React.useState(0);
+ return <button onClick={() => setNumber(number + 1)}>{number}</button>;
}
let element = <FunctionComponent />;
const root = createRoot(document.getElementById("root"));
root.render(element);
src\react\index.js
+export { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, useReducer, useState } from "./src/React";
src\react\src\React.js
+import { useReducer, useState } from "./ReactHooks";
import ReactSharedInternals from "./ReactSharedInternals";
+export { useReducer, useState, ReactSharedInternals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED };
src\react\src\ReactHooks.js
import ReactCurrentDispatcher from "./ReactCurrentDispatcher";
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return dispatcher;
}
export function useReducer(reducer, initialArg, init) {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg, init);
}
+export function useState(initialState) {
+ const dispatcher = resolveDispatcher();
+ return dispatcher.useState(initialState);
+}
src\shared\objectIs.js
const objectIs = Object.is;
export default objectIs;
src\react-reconciler\src\ReactFiberHooks.js
import ReactSharedInternals from "shared/ReactSharedInternals";
import { enqueueConcurrentHookUpdate } from "./ReactFiberConcurrentUpdates";
import { scheduleUpdateOnFiber } from "./ReactFiberWorkLoop";
+import is from "shared/objectIs";
const { ReactCurrentDispatcher } = ReactSharedInternals;
let currentlyRenderingFiber = null;
let workInProgressHook = null;
let currentHook = null;
const HooksDispatcherOnMountInDEV = {
useReducer: mountReducer,
+ useState: mountState,
};
const HooksDispatcherOnUpdateInDEV = {
useReducer: updateReducer,
+ useState: updateState,
};
+function basicStateReducer(state, action) {
+ return typeof action === "function" ? action(state) : action;
+}
function mountReducer(reducer, initialArg) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialArg;
const queue = {
pending: null,
dispatch: null,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function updateReducer(reducer) {
const hook = updateWorkInProgressHook()
const queue = hook.queue
queue.lastRenderedReducer = reducer
const current = currentHook
const pendingQueue = queue.pending
let newState = current.memoizedState
if (pendingQueue !== null) {
queue.pending = null
const first = pendingQueue.next
let update = first
do {
if (update.hasEagerState) {
newState = update.eagerState
} else {
const action = update.action
newState = reducer(newState, action)
}
update = update.next
} while (update !== null && update !== first)
}
hook.memoizedState = queue.lastRenderedState = newState
return [hook.memoizedState, queue.dispatch]
}
+function mountState(initialState) {
+ const hook = mountWorkInProgressHook();
+ hook.memoizedState = hook.baseState = initialState;
+ const queue = {
+ pending: null,
+ dispatch: null,
+ lastRenderedReducer: basicStateReducer,
+ lastRenderedState: initialState,
+ };
+ hook.queue = queue;
+ const dispatch = (queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue));
+ return [hook.memoizedState, dispatch];
+}
+function dispatchSetState(fiber, queue, action) {
+ const update = {
+ action,
+ hasEagerState: false,
+ eagerState: null,
+ next: null,
+ };
+ const lastRenderedReducer = queue.lastRenderedReducer;
+ const currentState = queue.lastRenderedState;
+ const eagerState = lastRenderedReducer(currentState, action);
+ update.hasEagerState = true;
+ update.eagerState = eagerState;
+ if (is(eagerState, currentState)) {
+ return;
+ }
+ const root = enqueueConcurrentHookUpdate(fiber, queue, update);
+ scheduleUpdateOnFiber(root, fiber);
+}
+function updateState(initialState) {
+ return updateReducer(basicStateReducer, initialState);
+}
function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function dispatchReducerAction(fiber, queue, action) {
const update = {
action,
next: null,
};
const root = enqueueConcurrentHookUpdate(fiber, queue, update);
scheduleUpdateOnFiber(root, fiber);
}
function updateWorkInProgressHook() {
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate
currentHook = current.memoizedState
} else {
currentHook = currentHook.next
}
const newHook = {
memoizedState: currentHook.memoizedState,
queue: currentHook.queue,
next: null
}
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook
} else {
workInProgressHook = workInProgressHook.next = newHook
}
return workInProgressHook
}
export function renderWithHooks(current, workInProgress, Component, props) {
currentlyRenderingFiber = workInProgress;
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
const children = Component(props);
currentlyRenderingFiber = null;
workInProgressHook = null;
currentHook = null;
return children;
}
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
+function FunctionComponent() {
+ const [number, setNumber] = React.useState(0);
+ return number === 0 ? (
+ <div onClick={() => setNumber(number + 1)} key="title" id="title">
+ title
+ </div>
+ ) : (
+ <div onClick={() => setNumber(number + 1)} key="title" id="title2">
+ title2
+ </div>
+ );
+}
let element = <FunctionComponent />;
const root = createRoot(document.getElementById("root"));
root.render(element);
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
function FunctionComponent() {
const [number, setNumber] = React.useState(0);
+ return number === 0 ? (
+ <div onClick={() => setNumber(number + 1)} key="title1" id="title">
+ title
+ </div>
+ ) : (
+ <div onClick={() => setNumber(number + 1)} key="title2" id="title2">
+ title2
+ </div>
);
}
let element = <FunctionComponent />;
const root = createRoot(document.getElementById("root"));
root.render(element);
src\react-reconciler\src\ReactFiberFlags.js
export const NoFlags = 0b00000000000000000000000000;
export const Placement = 0b00000000000000000000000010;
export const Update = 0b00000000000000000000000100;
+export const ChildDeletion = 0b00000000000000000000001000;
export const MutationMask = Placement | Update;
src\react-reconciler\src\ReactFiber.js
export function FiberNode(tag, pendingProps, key) {
this.tag = tag;
this.key = key;
this.type = null;
this.stateNode = null;
this.return = null;
this.child = null;
this.sibling = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
+ this.deletions = null;
this.alternate = null;
}
src\react-dom-bindings\src\client\ReactDOMHostConfig.js
+export function removeChild(parentInstance, child) {
+ parentInstance.removeChild(child);
+}
src\react-reconciler\src\ReactChildFiber.js
import { REACT_ELEMENT_TYPE } from "shared/ReactSymbols";
import isArray from "shared/isArray";
import { createFiberFromElement, FiberNode, createFiberFromText, createWorkInProgress } from "./ReactFiber";
+import { Placement, ChildDeletion } from "./ReactFiberFlags";
import { HostText } from "./ReactWorkTags";
function createChildReconciler(shouldTrackSideEffects) {
function useFiber(fiber, pendingProps) {
const clone = createWorkInProgress(fiber, pendingProps);
clone.index = 0;
clone.sibling = null;
return clone;
}
+ function deleteChild(returnFiber, childToDelete) {
+ if (!shouldTrackSideEffects) {
+ return;
+ }
+ const deletions = returnFiber.deletions;
+ if (deletions === null) {
+ returnFiber.deletions = [childToDelete];
+ returnFiber.flags |= ChildDeletion;
+ } else {
+ deletions.push(childToDelete);
+ }
+ }
function reconcileSingleElement(returnFiber, currentFirstChild, element) {
const key = element.key;
let child = currentFirstChild;
while (child !== null) {
if (child.key === key) {
const elementType = element.type;
if (child.type === elementType) {
const existing = useFiber(child, element.props);
existing.return = returnFiber;
return existing;
}
+ } else {
+ deleteChild(returnFiber, child);
+ }
child = child.sibling;
}
const created = createFiberFromElement(element);
created.return = returnFiber;
return created;
}
function placeSingleChild(newFiber) {
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags |= Placement;
}
return newFiber;
}
function reconcileSingleTextNode(returnFiber, currentFirstChild, content) {
const created = new FiberNode(HostText, { content }, null);
created.return = returnFiber;
return created;
}
function createChild(returnFiber, newChild) {
if ((typeof newChild === "string" && newChild !== "") || typeof newChild === "number") {
const created = createFiberFromText(`${newChild}`);
created.return = returnFiber;
return created;
}
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(newChild);
created.return = returnFiber;
return created;
}
default:
break;
}
}
return null;
}
function placeChild(newFiber, newIndex) {
newFiber.index = newIndex;
if (shouldTrackSideEffects) newFiber.flags |= Placement;
}
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) {
let resultingFirstChild = null;
let previousNewFiber = null;
let newIdx = 0;
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx]);
if (newFiber === null) {
continue;
}
placeChild(newFiber, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
function reconcileChildFibers(returnFiber, currentFirstChild, newChild) {
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild));
}
default:
break;
}
if (isArray(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild);
}
}
if (typeof newChild === "string") {
return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, newChild));
}
return null;
}
return reconcileChildFibers;
}
export const reconcileChildFibers = createChildReconciler(true);
export const mountChildFibers = createChildReconciler(false);
src\react-reconciler\src\ReactFiberCommitWork.js
import { HostRoot, HostComponent, HostText, FunctionComponent } from "./ReactWorkTags";
import { MutationMask, Placement, Update } from "./ReactFiberFlags";
import {
insertBefore,
appendChild,
commitUpdate,
+ removeChild,
} from "react-dom-bindings/src/client/ReactDOMHostConfig";
+let hostParent = null;
+function commitDeletionEffects(root, returnFiber, deletedFiber) {
+ let parent = returnFiber;
+ findParent: while (parent !== null) {
+ switch (parent.tag) {
+ case HostComponent: {
+ hostParent = parent.stateNode;
+ break findParent;
+ }
+ case HostRoot: {
+ hostParent = parent.stateNode.containerInfo;
+ break findParent;
+ }
+ default:
+ break;
+ }
+ parent = parent.return;
+ }
+ commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
+ hostParent = null;
+}
+function commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, deletedFiber) {
+ switch (deletedFiber.tag) {
+ case HostComponent:
+ case HostText: {
+ recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
+ if (hostParent !== null) {
+ removeChild(hostParent, deletedFiber.stateNode);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+function recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, parent) {
+ let child = parent.child;
+ while (child !== null) {
+ commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);
+ child = child.sibling;
+ }
+}
function recursivelyTraverseMutationEffects(root, parentFiber) {
+ const deletions = parentFiber.deletions;
+ if (deletions !== null) {
+ for (let i = 0; i < deletions.length; i++) {
+ const childToDelete = deletions[i];
+ commitDeletionEffects(root, parentFiber, childToDelete);
+ }
+ }
if (parentFiber.subtreeFlags & MutationMask) {
let { child } = parentFiber;
while (child !== null) {
commitMutationEffectsOnFiber(child, root);
child = child.sibling;
}
}
}
function isHostParent(fiber) {
return fiber.tag === HostComponent || fiber.tag === HostRoot;
}
function getHostParentFiber(fiber) {
let parent = fiber.return;
while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}
parent = parent.return;
}
return parent;
}
function insertOrAppendPlacementNode(node, before, parent) {
const { tag } = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost) {
const { stateNode } = node;
if (before) {
insertBefore(parent, stateNode, before);
} else {
appendChild(parent, stateNode);
}
} else {
const { child } = node;
if (child !== null) {
insertOrAppendPlacementNode(child, before, parent);
let { sibling } = child;
while (sibling !== null) {
insertOrAppendPlacementNode(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}
function getHostSibling(fiber) {
let node = fiber;
siblings: while (true) {
// 如果我们没有找到任何东西,让我们试试下一个弟弟
while (node.sibling === null) {
if (node.return === null || isHostParent(node.return)) {
// 如果我们是根Fiber或者父亲是原生节点,我们就是最后的弟弟
return null;
}
node = node.return;
}
// node.sibling.return = node.return
node = node.sibling;
while (node.tag !== HostComponent && node.tag !== HostText) {
// 如果它不是原生节点,并且,我们可能在其中有一个原生节点
// 试着向下搜索,直到找到为止
if (node.flags & Placement) {
// 如果我们没有孩子,可以试试弟弟
continue siblings;
} else {
// node.child.return = node
node = node.child;
}
} // Check if this host node is stable or about to be placed.
// 检查此原生节点是否稳定可以放置
if (!(node.flags & Placement)) {
// 找到它了!
return node.stateNode;
}
}
}
function commitPlacement(finishedWork) {
const parentFiber = getHostParentFiber(finishedWork);
switch (parentFiber.tag) {
case HostComponent: {
const parent = parentFiber.stateNode;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
case HostRoot: {
const parent = parentFiber.stateNode.containerInfo;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
default:
break;
}
}
function commitReconciliationEffects(finishedWork) {
const { flags } = finishedWork;
if (flags & Placement) {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}
}
export function commitMutationEffectsOnFiber(finishedWork, root) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case HostRoot: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
case FunctionComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
case HostComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
const instance = finishedWork.stateNode;
if (instance != null) {
const newProps = finishedWork.memoizedProps;
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
const updatePayload = finishedWork.updateQueue;
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(instance, updatePayload, type, oldProps, newProps, finishedWork);
}
}
}
break;
}
case HostText: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
default: {
break;
}
}
}
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
function FunctionComponent() {
const [number, setNumber] = React.useState(0);
+ return number === 0 ? (
+ <div onClick={() => setNumber(number + 1)} key="title1" id="title1">
+ title1
+ </div>
+ ) : (
+ <p onClick={() => setNumber(number + 1)} key="title1" id="title1">
+ title1
+ </p>
+ );
}
let element = <FunctionComponent />;
const root = createRoot(document.getElementById("root"));
root.render(element);
src\react-reconciler\src\ReactChildFiber.js
import { REACT_ELEMENT_TYPE } from "shared/ReactSymbols";
import isArray from "shared/isArray";
import { createFiberFromElement, FiberNode, createFiberFromText, createWorkInProgress } from "./ReactFiber";
import { Placement, ChildDeletion } from "./ReactFiberFlags";
import { HostText } from "./ReactWorkTags";
function createChildReconciler(shouldTrackSideEffects) {
function useFiber(fiber, pendingProps) {
const clone = createWorkInProgress(fiber, pendingProps);
clone.index = 0;
clone.sibling = null;
return clone;
}
function deleteChild(returnFiber, childToDelete) {
if (!shouldTrackSideEffects) {
return;
}
const deletions = returnFiber.deletions;
if (deletions === null) {
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(childToDelete);
}
}
+ function deleteRemainingChildren(returnFiber, currentFirstChild) {
+ if (!shouldTrackSideEffects) {
+ return null;
+ }
+ let childToDelete = currentFirstChild;
+ while (childToDelete !== null) {
+ deleteChild(returnFiber, childToDelete);
+ childToDelete = childToDelete.sibling;
+ }
+ return null;
+ }
function reconcileSingleElement(returnFiber, currentFirstChild, element) {
const key = element.key;
let child = currentFirstChild;
while (child !== null) {
if (child.key === key) {
const elementType = element.type;
if (child.type === elementType) {
+ deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.return = returnFiber;
return existing;
}
+ deleteRemainingChildren(returnFiber, child);
+ break;
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
const created = createFiberFromElement(element);
created.return = returnFiber;
return created;
}
function placeSingleChild(newFiber) {
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags |= Placement;
}
return newFiber;
}
function reconcileSingleTextNode(returnFiber, currentFirstChild, content) {
const created = new FiberNode(HostText, { content }, null);
created.return = returnFiber;
return created;
}
function createChild(returnFiber, newChild) {
if ((typeof newChild === "string" && newChild !== "") || typeof newChild === "number") {
const created = createFiberFromText(`${newChild}`);
created.return = returnFiber;
return created;
}
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(newChild);
created.return = returnFiber;
return created;
}
default:
break;
}
}
return null;
}
function placeChild(newFiber, newIndex) {
newFiber.index = newIndex;
if (shouldTrackSideEffects) newFiber.flags |= Placement;
}
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) {
let resultingFirstChild = null;
let previousNewFiber = null;
let newIdx = 0;
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx]);
if (newFiber === null) {
continue;
}
placeChild(newFiber, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
function reconcileChildFibers(returnFiber, currentFirstChild, newChild) {
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild));
}
default:
break;
}
if (isArray(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild);
}
}
if (typeof newChild === "string") {
return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, newChild));
}
return null;
}
return reconcileChildFibers;
}
export const reconcileChildFibers = createChildReconciler(true);
export const mountChildFibers = createChildReconciler(false);
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
function FunctionComponent() {
const [number, setNumber] = React.useState(0);
+ return number === 0 ? (
+ <ul key="container" onClick={() => setNumber(number + 1)}>
+ <li key="A">A</li>
+ <li key="B" id="B">
+ B
+ </li>
+ <li key="C">C</li>
+ </ul>
+ ) : (
+ <ul key="container" onClick={() => setNumber(number + 1)}>
+ <li key="B" id="B2">
+ B2
+ </li>
+ </ul>
+ );
}
let element = <FunctionComponent />;
const root = createRoot(document.getElementById("root"));
root.render(element);
节点移动
的逻辑key
相同,有的 type
不同,则更新属性,type
不同的删除老节点,删除新节点src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
function FunctionComponent() {
console.log("FunctionComponent");
const [number, setNumber] = React.useState(0);
+ return number === 0 ? (
+ <ul key="container" onClick={() => setNumber(number + 1)}>
+ <li key="A">A</li>
+ <li key="B" id="B">
+ B
+ </li>
+ <li key="C" id="C">
+ C
+ </li>
+ </ul>
+ ) : (
+ <ul key="container" onClick={() => setNumber(number + 1)}>
+ <li key="A">A2</li>
+ <p key="B" id="B2">
+ B2
+ </p>
+ <li key="C" id="C2">
+ C2
+ </li>
+ </ul>
+ );
}
let element = <FunctionComponent />;
const root = createRoot(document.getElementById("root"));
root.render(element);
src\react-reconciler\src\ReactChildFiber.js
import { REACT_ELEMENT_TYPE } from "shared/ReactSymbols";
import isArray from "shared/isArray";
import { createFiberFromElement, FiberNode, createFiberFromText, createWorkInProgress } from "./ReactFiber";
import { Placement, ChildDeletion } from "./ReactFiberFlags";
import { HostText } from "./ReactWorkTags";
function createChildReconciler(shouldTrackSideEffects) {
function useFiber(fiber, pendingProps) {
const clone = createWorkInProgress(fiber, pendingProps);
clone.index = 0;
clone.sibling = null;
return clone;
}
function deleteChild(returnFiber, childToDelete) {
if (!shouldTrackSideEffects) {
return;
}
const deletions = returnFiber.deletions;
if (deletions === null) {
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(childToDelete);
}
}
function deleteRemainingChildren(returnFiber, currentFirstChild) {
if (!shouldTrackSideEffects) {
return null;
}
let childToDelete = currentFirstChild;
while (childToDelete !== null) {
deleteChild(returnFiber, childToDelete);
childToDelete = childToDelete.sibling;
}
return null;
}
function reconcileSingleElement(returnFiber, currentFirstChild, element) {
const key = element.key;
let child = currentFirstChild;
while (child !== null) {
if (child.key === key) {
const elementType = element.type;
if (child.type === elementType) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.return = returnFiber;
return existing;
}
deleteRemainingChildren(returnFiber, child);
break;
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
const created = createFiberFromElement(element);
created.return = returnFiber;
return created;
}
function placeSingleChild(newFiber) {
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags |= Placement;
}
return newFiber;
}
function reconcileSingleTextNode(returnFiber, currentFirstChild, content) {
const created = new FiberNode(HostText, { content }, null);
created.return = returnFiber;
return created;
}
function createChild(returnFiber, newChild) {
if ((typeof newChild === "string" && newChild !== "") || typeof newChild === "number") {
const created = createFiberFromText(`${newChild}`);
created.return = returnFiber;
return created;
}
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(newChild);
created.return = returnFiber;
return created;
}
default:
break;
}
}
return null;
}
function placeChild(newFiber, newIndex) {
+ newFiber.index = newIndex;
+ if (!shouldTrackSideEffects) {
+ return;
+ }
+ const current = newFiber.alternate;
+ if (current !== null) {
+ return;
+ } else {
+ newFiber.flags |= Placement;
+ }
}
+ function updateElement(returnFiber, current, element) {
+ const elementType = element.type;
+ if (current !== null) {
+ if (current.type === elementType) {
+ const existing = useFiber(current, element.props);
+ existing.return = returnFiber;
+ return existing;
+ }
+ }
+ const created = createFiberFromElement(element);
+ created.return = returnFiber;
+ return created;
+ }
+ function updateSlot(returnFiber, oldFiber, newChild) {
+ const key = oldFiber !== null ? oldFiber.key : null;
+ if (typeof newChild === "object" && newChild !== null) {
+ switch (newChild.$$typeof) {
+ case REACT_ELEMENT_TYPE: {
+ if (newChild.key === key) {
+ return updateElement(returnFiber, oldFiber, newChild);
+ }
+ }
+ default:
+ return null;
+ }
+ return null
+ }
+ }
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) {
let resultingFirstChild = null;
let previousNewFiber = null;
let newIdx = 0;
+ let oldFiber = currentFirstChild;
+ let nextOldFiber = null;
+ for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
+ nextOldFiber = oldFiber.sibling;
+ const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx]);
+ if (newFiber === null) {
+ break;
+ }
+ if (shouldTrackSideEffects) {
+ if (oldFiber && newFiber.alternate === null) {
+ deleteChild(returnFiber, oldFiber);
+ }
+ }
+ placeChild(newFiber, newIdx);
+ if (previousNewFiber === null) {
+ resultingFirstChild = newFiber;
+ } else {
+ previousNewFiber.sibling = newFiber;
+ }
+ previousNewFiber = newFiber;
+ oldFiber = nextOldFiber;
+ }
+ if (newIdx === newChildren.length) {
+ deleteRemainingChildren(returnFiber, oldFiber);
+ return resultingFirstChild;
+ }
+ if (oldFiber === null) {
+ for (; newIdx < newChildren.length; newIdx++) {
+ const newFiber = createChild(returnFiber, newChildren[newIdx]);
+ if (newFiber === null) {
+ continue;
+ }
+ placeChild(newFiber, newIdx);
+ if (previousNewFiber === null) {
+ resultingFirstChild = newFiber;
+ } else {
+ previousNewFiber.sibling = newFiber;
+ }
+ previousNewFiber = newFiber;
+ }
+ }
return resultingFirstChild;
}
function reconcileChildFibers(returnFiber, currentFirstChild, newChild) {
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild));
}
default:
break;
}
if (isArray(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild);
}
}
if (typeof newChild === "string") {
return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, newChild));
}
return null;
}
return reconcileChildFibers;
}
export const reconcileChildFibers = createChildReconciler(true);
export const mountChildFibers = createChildReconciler(false);
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
function FunctionComponent() {
console.log("FunctionComponent");
const [number, setNumber] = React.useState(0);
+ return number === 0 ? (
+ <ul key="container" onClick={() => setNumber(number + 1)}>
+ <li key="A">A</li>
+ <li key="B" id="B">
+ B
+ </li>
+ <li key="C">C</li>
+ </ul>
+ ) : (
+ <ul key="container" onClick={() => setNumber(number + 1)}>
+ <li key="A">A</li>
+ <li key="B" id="B2">
+ B2
+ </li>
+ <li key="C">C2</li>
+ <li key="D">D</li>
+ </ul>
+ );
}
let element = <FunctionComponent />;
const root = createRoot(document.getElementById("root"));
root.render(element);
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
function FunctionComponent() {
console.log("FunctionComponent");
const [number, setNumber] = React.useState(0);
+ return number === 0 ? (
+ <ul key="container" onClick={() => setNumber(number + 1)}>
+ <li key="A">A</li>
+ <li key="B" id="B">
+ B
+ </li>
+ <li key="C">C</li>
+ </ul>
+ ) : (
+ <ul key="container" onClick={() => setNumber(number + 1)}>
+ <li key="A">A</li>
+ <li key="B" id="B2">
+ B2
+ </li>
+ </ul>
+ );
}
let element = <FunctionComponent />;
const root = createRoot(document.getElementById("root"));
root.render(element);
lastPlacedIndex
变量,表示不需要移动的老节点的索引src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
function FunctionComponent() {
console.log("FunctionComponent");
const [number, setNumber] = React.useState(0);
return number === 0 ? (
<ul key="container" onClick={() => setNumber(number + 1)}>
+ <li key="A">A</li>
+ <li key="B" id="b">
+ B
+ </li>
+ <li key="C">C</li>
+ <li key="D">D</li>
+ <li key="E">E</li>
+ <li key="F">F</li>
</ul>
) : (
<ul key="container" onClick={() => setNumber(number + 1)}>
+ <li key="A">A2</li>
+ <li key="C">C2</li>
+ <li key="E">E2</li>
+ <li key="B" id="b2">
+ B2
+ </li>
+ <li key="G">G</li>
+ <li key="D">D2</li>
</ul>
);
}
let element = <FunctionComponent />;
const root = createRoot(document.getElementById("root"));
root.render(element);
src\react-reconciler\src\ReactFiber.js
export function FiberNode(tag, pendingProps, key) {
this.tag = tag;
this.key = key;
this.type = null;
this.stateNode = null;
this.return = null;
this.child = null;
this.sibling = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
this.alternate = null;
+ this.index = 0;
}
src\react-reconciler\src\ReactFiberWorkLoop.js
import { scheduleCallback } from "scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
import { completeWork } from "./ReactFiberCompleteWork";
import { MutationMask, NoFlags, Placement, Update,ChildDeletion } from "./ReactFiberFlags";
import { commitMutationEffectsOnFiber } from "./ReactFiberCommitWork";
import { finishQueueingConcurrentUpdates } from "./ReactFiberConcurrentUpdates";
import { FunctionComponent, IndeterminateComponent, HostRoot, HostComponent, HostText } from "./ReactWorkTags";
let workInProgress = null;
export function scheduleUpdateOnFiber(root) {
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}
function performConcurrentWorkOnRoot(root) {
renderRootSync(root);
const finishedWork = root.current.alternate;
+ printFiber(finishedWork);
+ console.log(`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`);
root.finishedWork = finishedWork;
commitRoot(root);
}
function commitRoot(root) {
const { finishedWork } = root;
const subtreeHasEffects = (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
commitMutationEffectsOnFiber(finishedWork, root);
}
root.current = finishedWork;
}
function prepareFreshStack(root) {
workInProgress = createWorkInProgress(root.current, null);
finishQueueingConcurrentUpdates();
}
function renderRootSync(root) {
prepareFreshStack(root);
workLoopSync();
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
completeWork(current, completedWork);
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
}
+function printFiber(fiber) {
+ /*
+ fiber.flags &= ~Forked;
+ fiber.flags &= ~PlacementDEV;
+ fiber.flags &= ~Snapshot;
+ fiber.flags &= ~PerformedWork;
+ */
+ if (fiber.flags !== 0) {
+ console.log(
+ getFlags(fiber.flags),
+ getTag(fiber.tag),
+ typeof fiber.type === "function" ? fiber.type.name : fiber.type,
+ fiber.memoizedProps
+ );
+ if (fiber.deletions) {
+ for (let i = 0; i < fiber.deletions.length; i++) {
+ const childToDelete = fiber.deletions[i];
+ console.log(getTag(childToDelete.tag), childToDelete.type, childToDelete.+memoizedProps);
+ }
+ }
+ }
+ let child = fiber.child;
+ while (child) {
+ printFiber(child);
+ child = child.sibling;
+ }
+}
+function getTag(tag) {
+ switch (tag) {
+ case FunctionComponent:
+ return `FunctionComponent`;
+ case HostRoot:
+ return `HostRoot`;
+ case HostComponent:
+ return `HostComponent`;
+ case HostText:
+ return HostText;
+ default:
+ return tag;
+ }
+}
+function getFlags(flags) {
+ if (flags === (Update | Placement | ChildDeletion)) {
+ return `自己移动和子元素有删除`;
+ }
+ if (flags === (ChildDeletion | Update)) {
+ return `自己有更新和子元素有删除`;
+ }
+ if (flags === ChildDeletion) {
+ return `子元素有删除`;
+ }
+ if (flags === (Placement | Update)) {
+ return `移动并更新`;
+ }
+ if (flags === Placement) {
+ return `插入`;
+ }
+ if (flags === Update) {
+ return `更新`;
+ }
+ return flags;
+}
src\react-reconciler\src\ReactChildFiber.js
import { REACT_ELEMENT_TYPE } from "shared/ReactSymbols";
import isArray from "shared/isArray";
import { createFiberFromElement, FiberNode, createFiberFromText, createWorkInProgress } from "./ReactFiber";
import { Placement, ChildDeletion } from "./ReactFiberFlags";
import { HostText } from "./ReactWorkTags";
function createChildReconciler(shouldTrackSideEffects) {
function useFiber(fiber, pendingProps) {
const clone = createWorkInProgress(fiber, pendingProps);
clone.index = 0;
clone.sibling = null;
return clone;
}
function deleteChild(returnFiber, childToDelete) {
if (!shouldTrackSideEffects) {
return;
}
const deletions = returnFiber.deletions;
if (deletions === null) {
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(childToDelete);
}
}
function deleteRemainingChildren(returnFiber, currentFirstChild) {
if (!shouldTrackSideEffects) {
return null;
}
let childToDelete = currentFirstChild;
while (childToDelete !== null) {
deleteChild(returnFiber, childToDelete);
childToDelete = childToDelete.sibling;
}
return null;
}
function reconcileSingleElement(returnFiber, currentFirstChild, element) {
const key = element.key;
let child = currentFirstChild;
while (child !== null) {
if (child.key === key) {
const elementType = element.type;
if (child.type === elementType) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.return = returnFiber;
return existing;
}
deleteRemainingChildren(returnFiber, child);
break;
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
const created = createFiberFromElement(element);
created.return = returnFiber;
return created;
}
function placeSingleChild(newFiber) {
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags |= Placement;
}
return newFiber;
}
function reconcileSingleTextNode(returnFiber, currentFirstChild, content) {
const created = new FiberNode(HostText, { content }, null);
created.return = returnFiber;
return created;
}
function createChild(returnFiber, newChild) {
if ((typeof newChild === "string" && newChild !== "") || typeof newChild === "number") {
const created = createFiberFromText(`${newChild}`);
created.return = returnFiber;
return created;
}
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(newChild);
created.return = returnFiber;
return created;
}
default:
break;
}
}
return null;
}
+ function placeChild(newFiber, lastPlacedIndex, newIndex) {
+ newFiber.index = newIndex;
+ if (!shouldTrackSideEffects) {
+ return lastPlacedIndex;
+ }
+ const current = newFiber.alternate;
+ if (current !== null) {
+ const oldIndex = current.index;
+ if (oldIndex < lastPlacedIndex) {
+ newFiber.flags |= Placement;
+ return lastPlacedIndex;
+ } else {
+ return oldIndex;
+ }
+ } else {
+ newFiber.flags |= Placement;
+ return lastPlacedIndex;
+ }
+ }
function updateElement(returnFiber, current, element) {
const elementType = element.type;
if (current !== null) {
if (current.type === elementType) {
const existing = useFiber(current, element.props);
existing.return = returnFiber;
return existing;
}
}
const created = createFiberFromElement(element);
created.return = returnFiber;
return created;
}
function updateSlot(returnFiber, oldFiber, newChild) {
const key = oldFiber !== null ? oldFiber.key : null;
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
if (newChild.key === key) {
return updateElement(returnFiber, oldFiber, newChild);
} else {
return null;
}
}
default:
return null;
}
}
}
+ function mapRemainingChildren(returnFiber, currentFirstChild) {
+ const existingChildren = new Map();
+ let existingChild = currentFirstChild;
+ while (existingChild !== null) {
+ if (existingChild.key !== null) {
+ existingChildren.set(existingChild.key, existingChild);
+ } else {
+ existingChildren.set(existingChild.index, existingChild);
+ }
+ existingChild = existingChild.sibling;
+ }
+ return existingChildren;
+ }
+ function updateTextNode(returnFiber, current, textContent) {
+ if (current === null || current.tag !== HostText) {
+ const created = createFiberFromText(textContent);
+ created.return = returnFiber;
+ return created;
+ } else {
+ const existing = useFiber(current, textContent);
+ existing.return = returnFiber;
+ return existing;
+ }
+ }
+ function updateFromMap(existingChildren, returnFiber, newIdx, newChild) {
+ if ((typeof newChild === "string" && newChild !== "") || typeof newChild === "number") {
+ const matchedFiber = existingChildren.get(newIdx) || null;
+ return updateTextNode(returnFiber, matchedFiber, "" + newChild);
+ }
+ if (typeof newChild === "object" && newChild !== null) {
+ switch (newChild.$$typeof) {
+ case REACT_ELEMENT_TYPE: {
+ const matchedFiber = existingChildren.get(newChild.key === null ? newIdx : newChild.key) || null;
+ return updateElement(returnFiber, matchedFiber, newChild);
+ }
+ }
+ }
+ return null;
+ }
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) {
let resultingFirstChild = null;
let previousNewFiber = null;
let newIdx = 0;
let oldFiber = currentFirstChild;
let nextOldFiber = null;
+ let lastPlacedIndex = 0;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
nextOldFiber = oldFiber.sibling;
const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx]);
if (newFiber === null) {
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
deleteChild(returnFiber, oldFiber);
}
}
+ lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (newIdx === newChildren.length) {
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
if (oldFiber === null) {
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx]);
if (newFiber === null) {
continue;
}
+ lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
+ const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
+ for (; newIdx < newChildren.length; newIdx++) {
+ const newFiber = updateFromMap(existingChildren, returnFiber, newIdx, newChildren[newIdx]);
+ if (newFiber !== null) {
+ if (shouldTrackSideEffects) {
+ if (newFiber.alternate !== null) {
+ existingChildren.delete(newFiber.key === null ? newIdx : newFiber.key);
+ }
+ }
+ lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
+ if (previousNewFiber === null) {
+ resultingFirstChild = newFiber;
+ } else {
+ previousNewFiber.sibling = newFiber;
+ }
+ previousNewFiber = newFiber;
+ }
+ }
+ if (shouldTrackSideEffects) {
+ existingChildren.forEach((child) => deleteChild(returnFiber, child));
+ }
return resultingFirstChild;
}
function reconcileChildFibers(returnFiber, currentFirstChild, newChild) {
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild));
}
default:
break;
}
if (isArray(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild);
}
}
if (typeof newChild === "string") {
return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, newChild));
}
return null;
}
return reconcileChildFibers;
}
export const reconcileChildFibers = createChildReconciler(true);
export const mountChildFibers = createChildReconciler(false);
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
function Counter() {
+ const [number, setNumber] = React.useState(0);
+ React.useEffect(() => {
+ console.log("useEffect1");
+ return () => {
+ console.log("destroy useEffect1");
+ };
+ });
+ React.useEffect(() => {
+ console.log("useEffect2");
+ return () => {
+ console.log("destroy useEffect2");
+ };
+ });
+ React.useEffect(() => {
+ console.log("useEffect3");
+ return () => {
+ console.log("destroy useEffect3");
+ };
+ });
+ return (
+ <div
+ onClick={() => {
+ setNumber(number + 1);
+ }}
+ >
+ {number}
+ </div>
+ );
}
let element = <Counter />;
const root = createRoot(document.getElementById("root"));
root.render(element);
src\react\index.js
+export { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, useReducer, useState, useEffect } from "./src/React";
src\react\src\React.js
+import { useReducer, useState, useEffect } from "./ReactHooks";
import ReactSharedInternals from "./ReactSharedInternals";
+export { useReducer, useState, useEffect, ReactSharedInternals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED };
src\react\src\ReactHooks.js
import ReactCurrentDispatcher from "./ReactCurrentDispatcher";
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return dispatcher;
}
export function useReducer(reducer, initialArg, init) {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg, init);
}
export function useState(initialState) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
+export function useEffect(create,deps) {
+ const dispatcher = resolveDispatcher();
+ return dispatcher.useEffect(create,deps);
+}
src\react-reconciler\src\ReactHookEffectTags.js
export const HasEffect = 0b0001;
export const Passive = 0b1000;
src\react-reconciler\src\ReactFiberFlags.js
export const NoFlags = 0b00000000000000000000000000;
export const Placement = 0b00000000000000000000000010;
export const Update = 0b00000000000000000000000100;
export const ChildDeletion = 0b00000000000000000000001000;
export const MutationMask = Placement | Update;
+export const Passive = 0b00000000000000010000000000;
src\react-reconciler\src\ReactFiberHooks.js
import ReactSharedInternals from "shared/ReactSharedInternals";
import { enqueueConcurrentHookUpdate } from "./ReactFiberConcurrentUpdates";
import { scheduleUpdateOnFiber } from "./ReactFiberWorkLoop";
import is from "shared/objectIs";
+import { Passive as PassiveEffect } from "./ReactFiberFlags";
+import { HasEffect as HookHasEffect, Passive as HookPassive } from "./ReactHookEffectTags";
const { ReactCurrentDispatcher } = ReactSharedInternals;
let currentlyRenderingFiber = null;
let workInProgressHook = null;
let currentHook = null;
const HooksDispatcherOnMountInDEV = {
useReducer: mountReducer,
useState: mountState,
+ useEffect: mountEffect,
};
const HooksDispatcherOnUpdateInDEV = {
useReducer: updateReducer,
useState: updateState,
+ useEffect: updateEffect,
};
+function updateEffect(create, deps) {
+ return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
+}
+function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
+ const hook = updateWorkInProgressHook();
+ const nextDeps = deps === undefined ? null : deps;
+ let destroy;
+ if (currentHook !== null) {
+ const prevEffect = currentHook.memoizedState;
+ destroy = prevEffect.destroy;
+ if (nextDeps !== null) {
+ const prevDeps = prevEffect.deps;
+ if (areHookInputsEqual(nextDeps, prevDeps)) {
+ hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
+ return;
+ }
+ }
+ }
+ currentlyRenderingFiber.flags |= fiberFlags;
+ hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, destroy, nextDeps);
+}
+function areHookInputsEqual(nextDeps, prevDeps) {
+ if (prevDeps === null) {
+ return false;
+ }
+ for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
+ if (is(nextDeps[i], prevDeps[i])) {
+ continue;
+ }
+ return false;
+ }
+
+ return true;
+}
+function mountEffect(create, deps) {
+ return mountEffectImpl(PassiveEffect, HookPassive, create, deps);
+}
+function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
+ const hook = mountWorkInProgressHook();
+ const nextDeps = deps === undefined ? null : deps;
+ currentlyRenderingFiber.flags |= fiberFlags;
+ hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, undefined, nextDeps);
+}
+function pushEffect(tag, create, destroy, deps) {
+ const effect = {
+ tag,
+ create,
+ destroy,
+ deps,
+ next: null,
+ };
+ let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
+ if (componentUpdateQueue === null) {
+ componentUpdateQueue = createFunctionComponentUpdateQueue();
+ currentlyRenderingFiber.updateQueue = componentUpdateQueue;
+ componentUpdateQueue.lastEffect = effect.next = effect;
+ } else {
+ const lastEffect = componentUpdateQueue.lastEffect;
+ if (lastEffect === null) {
+ componentUpdateQueue.lastEffect = effect.next = effect;
+ } else {
+ const firstEffect = lastEffect.next;
+ lastEffect.next = effect;
+ effect.next = firstEffect;
+ componentUpdateQueue.lastEffect = effect;
+ }
+ }
+ return effect;
+}
+function createFunctionComponentUpdateQueue() {
+ return {
+ lastEffect: null,
+ };
+}
function basicStateReducer(state, action) {
return typeof action === "function" ? action(state) : action;
}
function mountReducer(reducer, initialArg) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialArg;
const queue = {
pending: null,
dispatch: null,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function updateReducer(reducer) {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const current = currentHook;
const pendingQueue = queue.pending;
let baseQueue = null;
let newState = current.memoizedState;
if (pendingQueue !== null) {
baseQueue = pendingQueue;
queue.pending = null;
}
if (baseQueue !== null) {
const first = baseQueue.next;
let update = first;
do {
if (update.hasEagerState) {
newState = update.eagerState;
} else {
const action = update.action;
newState = reducer(newState, action);
}
update = update.next;
} while (update !== null && update !== first);
}
hook.memoizedState = newState;
queue.lastRenderedState = newState;
const dispatch = queue.dispatch;
return [hook.memoizedState, dispatch];
}
function mountState(initialState) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialState;
const queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function dispatchSetState(fiber, queue, action) {
const update = {
action,
hasEagerState: false,
eagerState: null,
next: null,
};
const lastRenderedReducer = queue.lastRenderedReducer;
const currentState = queue.lastRenderedState;
const eagerState = lastRenderedReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
return;
}
const root = enqueueConcurrentHookUpdate(fiber, queue, update);
scheduleUpdateOnFiber(root, fiber);
}
function updateState(initialState) {
return updateReducer(basicStateReducer, initialState);
}
function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function dispatchReducerAction(fiber, queue, action) {
const update = {
action,
next: null,
};
const root = enqueueConcurrentHookUpdate(fiber, queue, update);
scheduleUpdateOnFiber(root, fiber);
}
function updateWorkInProgressHook() {
let nextCurrentHook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
currentHook = nextCurrentHook;
const newHook = {
memoizedState: currentHook.memoizedState,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
export function renderWithHooks(current, workInProgress, Component, props) {
currentlyRenderingFiber = workInProgress;
+ workInProgress.updateQueue = null;
+ workInProgress.memoizedState = null;
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
const children = Component(props);
currentlyRenderingFiber = null;
workInProgressHook = null;
currentHook = null;
return children;
}
src\react-reconciler\src\ReactFiberWorkLoop.js
import { scheduleCallback } from "scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
import { completeWork } from "./ReactFiberCompleteWork";
+import { MutationMask, NoFlags, Placement, Update, ChildDeletion, Passive } from "./ReactFiberFlags";
+import { commitMutationEffects, commitPassiveUnmountEffects, commitPassiveMountEffects } from "./ReactFiberCommitWork";
import { finishQueueingConcurrentUpdates } from "./ReactFiberConcurrentUpdates";
import { FunctionComponent, IndeterminateComponent, HostRoot, HostComponent, HostText } from "./ReactWorkTags";
let workInProgress = null;
+let rootDoesHavePassiveEffects = false;
+let rootWithPendingPassiveEffects = null;
export function scheduleUpdateOnFiber(root) {
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}
function performConcurrentWorkOnRoot(root) {
renderRootSync(root);
const finishedWork = root.current.alternate;
printFiber(finishedWork);
console.log(`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`);
root.finishedWork = finishedWork;
commitRoot(root);
}
+export function flushPassiveEffects() {
+ if (rootWithPendingPassiveEffects !== null) {
+ const root = rootWithPendingPassiveEffects;
+ commitPassiveUnmountEffects(root.current);
+ commitPassiveMountEffects(root, root.current);
+ }
+}
function commitRoot(root) {
const { finishedWork } = root;
+ if ((finishedWork.subtreeFlags & Passive) !== NoFlags || (finishedWork.flags & Passive) !== NoFlags) {
+ if (!rootDoesHavePassiveEffects) {
+ rootDoesHavePassiveEffects = true;
+ scheduleCallback(flushPassiveEffects);
+ }
+ }
const subtreeHasEffects = (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
+ commitMutationEffects(finishedWork, root);
+ root.current = finishedWork;
+ if (rootDoesHavePassiveEffects) {
+ rootDoesHavePassiveEffects = false;
+ rootWithPendingPassiveEffects = root;
+ }
}
root.current = finishedWork;
}
function prepareFreshStack(root) {
workInProgress = createWorkInProgress(root.current, null);
finishQueueingConcurrentUpdates();
}
function renderRootSync(root) {
prepareFreshStack(root);
workLoopSync();
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
completeWork(current, completedWork);
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
}
function printFiber(fiber) {
/*
fiber.flags &= ~Forked;
fiber.flags &= ~PlacementDEV;
fiber.flags &= ~Snapshot;
fiber.flags &= ~PerformedWork;
*/
if (fiber.flags !== 0) {
console.log(
getFlags(fiber.flags),
getTag(fiber.tag),
typeof fiber.type === "function" ? fiber.type.name : fiber.type,
fiber.memoizedProps
);
if (fiber.deletions) {
for (let i = 0; i < fiber.deletions.length; i++) {
const childToDelete = fiber.deletions[i];
console.log(getTag(childToDelete.tag), childToDelete.type, childToDelete.memoizedProps);
}
}
}
let child = fiber.child;
while (child) {
printFiber(child);
child = child.sibling;
}
}
function getTag(tag) {
switch (tag) {
case FunctionComponent:
return `FunctionComponent`;
case HostRoot:
return `HostRoot`;
case HostComponent:
return `HostComponent`;
case HostText:
return HostText;
default:
return tag;
}
}
function getFlags(flags) {
if (flags === (Update | Placement | ChildDeletion)) {
return `自己移动和子元素有删除`;
}
if (flags === (ChildDeletion | Update)) {
return `自己有更新和子元素有删除`;
}
if (flags === ChildDeletion) {
return `子元素有删除`;
}
if (flags === (Placement | Update)) {
return `移动并更新`;
}
if (flags === Placement) {
return `插入`;
}
if (flags === Update) {
return `更新`;
}
return flags;
}
src\react-reconciler\src\ReactFiberCommitWork.js
import { HostRoot, HostComponent, HostText, FunctionComponent } from "./ReactWorkTags";
+import { MutationMask, Placement, Update, Passive } from "./ReactFiberFlags";
import { insertBefore, appendChild, commitUpdate, removeChild } from "react-dom-bindings/src/client/ReactDOMHostConfig";
+import { HasEffect as HookHasEffect, Passive as HookPassive } from "./ReactHookEffectTags";
+export function commitMutationEffects(finishedWork, root) {
+ commitMutationEffectsOnFiber(finishedWork, root);
+}
+export function commitPassiveUnmountEffects(finishedWork) {
+ commitPassiveUnmountOnFiber(finishedWork);
+}
+function commitPassiveUnmountOnFiber(finishedWork) {
+ switch (finishedWork.tag) {
+ case FunctionComponent: {
+ recursivelyTraversePassiveUnmountEffects(finishedWork);
+ if (finishedWork.flags & Passive) {
+ commitHookPassiveUnmountEffects(finishedWork, finishedWork.return, HookPassive | HookHasEffect);
+ }
+ break;
+ }
+ default: {
+ recursivelyTraversePassiveUnmountEffects(finishedWork);
+ break;
+ }
+ }
+}
+function recursivelyTraversePassiveUnmountEffects(parentFiber) {
+ if (parentFiber.subtreeFlags & Passive) {
+ let child = parentFiber.child;
+ while (child !== null) {
+ commitPassiveUnmountOnFiber(child);
+ child = child.sibling;
+ }
+ }
+}
+function commitHookPassiveUnmountEffects(finishedWork, nearestMountedAncestor, +hookFlags) {
+ commitHookEffectListUnmount(hookFlags, finishedWork, nearestMountedAncestor);
+}
+
+function commitHookEffectListUnmount(flags, finishedWork) {
+ const updateQueue = finishedWork.updateQueue;
+ const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
+ if (lastEffect !== null) {
+ const firstEffect = lastEffect.next;
+ let effect = firstEffect;
+ do {
+ if ((effect.tag & flags) === flags) {
+ const destroy = effect.destroy;
+ effect.destroy = undefined;
+ if (destroy !== undefined) {
+ destroy();
+ }
+ }
+ effect = effect.next;
+ } while (effect !== firstEffect);
+ }
+}
+export function commitPassiveMountEffects(root, finishedWork) {
+ commitPassiveMountOnFiber(root, finishedWork);
+}
+function commitPassiveMountOnFiber(finishedRoot, finishedWork) {
+ const flags = finishedWork.flags;
+ switch (finishedWork.tag) {
+ case FunctionComponent: {
+ recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork);
+ if (flags & Passive) {
+ commitHookPassiveMountEffects(finishedWork, HookPassive | HookHasEffect);
+ }
+ break;
+ }
+ case HostRoot: {
+ recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork);
+ break;
+ }
+ default:
+ break;
+ }
+}
+function commitHookPassiveMountEffects(finishedWork, hookFlags) {
+ commitHookEffectListMount(hookFlags, finishedWork);
+}
+function commitHookEffectListMount(flags, finishedWork) {
+ const updateQueue = finishedWork.updateQueue;
+ const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
+ if (lastEffect !== null) {
+ const firstEffect = lastEffect.next;
+ let effect = firstEffect;
+ do {
+ if ((effect.tag & flags) === flags) {
+ const create = effect.create;
+ effect.destroy = create();
+ }
+ effect = effect.next;
+ } while (effect !== firstEffect);
+ }
+}
+function recursivelyTraversePassiveMountEffects(root, parentFiber) {
+ if (parentFiber.subtreeFlags & Passive) {
+ let child = parentFiber.child;
+ while (child !== null) {
+ commitPassiveMountOnFiber(root, child);
+ child = child.sibling;
+ }
+ }
+}
let hostParent = null;
function commitDeletionEffects(root, returnFiber, deletedFiber) {
let parent = returnFiber;
findParent: while (parent !== null) {
switch (parent.tag) {
case HostComponent: {
hostParent = parent.stateNode;
break findParent;
}
case HostRoot: {
hostParent = parent.stateNode.containerInfo;
break findParent;
}
default:
break;
}
parent = parent.return;
}
commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
hostParent = null;
}
function commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, deletedFiber) {
switch (deletedFiber.tag) {
case HostComponent:
case HostText: {
recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
if (hostParent !== null) {
removeChild(hostParent, deletedFiber.stateNode);
}
break;
}
default:
break;
}
}
function recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, parent) {
let child = parent.child;
while (child !== null) {
commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);
child = child.sibling;
}
}
function recursivelyTraverseMutationEffects(root, parentFiber) {
const deletions = parentFiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
commitDeletionEffects(root, parentFiber, childToDelete);
}
}
if (parentFiber.subtreeFlags & MutationMask) {
let { child } = parentFiber;
while (child !== null) {
commitMutationEffectsOnFiber(child, root);
child = child.sibling;
}
}
}
function isHostParent(fiber) {
return fiber.tag === HostComponent || fiber.tag === HostRoot;
}
function getHostParentFiber(fiber) {
let parent = fiber.return;
while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}
parent = parent.return;
}
return parent;
}
function insertOrAppendPlacementNode(node, before, parent) {
const { tag } = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost) {
const { stateNode } = node;
if (before) {
insertBefore(parent, stateNode, before);
} else {
appendChild(parent, stateNode);
}
} else {
const { child } = node;
if (child !== null) {
insertOrAppendPlacementNode(child, before, parent);
let { sibling } = child;
while (sibling !== null) {
insertOrAppendPlacementNode(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}
function getHostSibling(fiber) {
let node = fiber;
siblings: while (true) {
// 如果我们没有找到任何东西,让我们试试下一个弟弟
while (node.sibling === null) {
if (node.return === null || isHostParent(node.return)) {
// 如果我们是根Fiber或者父亲是原生节点,我们就是最后的弟弟
return null;
}
node = node.return;
}
// node.sibling.return = node.return
node = node.sibling;
while (node.tag !== HostComponent && node.tag !== HostText) {
// 如果它不是原生节点,并且,我们可能在其中有一个原生节点
// 试着向下搜索,直到找到为止
if (node.flags & Placement) {
// 如果我们没有孩子,可以试试弟弟
continue siblings;
} else {
// node.child.return = node
node = node.child;
}
} // Check if this host node is stable or about to be placed.
// 检查此原生节点是否稳定可以放置
if (!(node.flags & Placement)) {
// 找到它了!
return node.stateNode;
}
}
}
function commitPlacement(finishedWork) {
const parentFiber = getHostParentFiber(finishedWork);
switch (parentFiber.tag) {
case HostComponent: {
const parent = parentFiber.stateNode;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
case HostRoot: {
const parent = parentFiber.stateNode.containerInfo;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
default:
break;
}
}
function commitReconciliationEffects(finishedWork) {
const { flags } = finishedWork;
if (flags & Placement) {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}
}
export function commitMutationEffectsOnFiber(finishedWork, root) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case HostRoot: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
case FunctionComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
case HostComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
const instance = finishedWork.stateNode;
if (instance != null) {
const newProps = finishedWork.memoizedProps;
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
const updatePayload = finishedWork.updateQueue;
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(instance, updatePayload, type, oldProps, newProps, finishedWork);
}
}
}
break;
}
case HostText: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
default: {
break;
}
}
}
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
function Counter() {
const [number, setNumber] = React.useState(0);
React.useEffect(() => {
console.log("useEffect1");
return () => {
console.log("destroy useEffect1");
};
});
+ React.useLayoutEffect(() => {
+ console.log("useLayoutEffect2");
+ return () => {
+ console.log("destroy useLayoutEffect2");
+ };
+ });
React.useEffect(() => {
console.log("useEffect3");
return () => {
console.log("destroy useEffect3");
};
});
return (
<div
onClick={() => {
setNumber(number + 1);
}}
>
{number}
</div>
);
}
let element = <Counter />;
const root = createRoot(document.getElementById("root"));
root.render(element);
src\react\index.js
export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
useReducer,
useState,
useEffect,
+ useLayoutEffect,
} from "./src/React";
src\react\src\React.js
import { useReducer, useState, useEffect, useLayoutEffect } from "./ReactHooks";
import ReactSharedInternals from "./ReactSharedInternals";
export {
useReducer,
useState,
useEffect,
+ useLayoutEffect,
ReactSharedInternals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
};
src\react\src\ReactHooks.js
import ReactCurrentDispatcher from "./ReactCurrentDispatcher";
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return dispatcher;
}
export function useReducer(reducer, initialArg, init) {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg, init);
}
export function useState(initialState) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
export function useEffect(create,deps) {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create,deps);
}
+export function useLayoutEffect(create,deps) {
+ const dispatcher = resolveDispatcher();
+ return dispatcher.useLayoutEffect(create,deps);
+}
src\react-reconciler\src\ReactHookEffectTags.js
export const HasEffect = 0b0001;
export const Passive = 0b1000;
+export const Layout = 0b0100;
src\react-reconciler\src\ReactFiberFlags.js
export const NoFlags = 0b00000000000000000000000000;
export const Placement = 0b00000000000000000000000010;
export const Update = 0b00000000000000000000000100;
export const ChildDeletion = 0b00000000000000000000001000;
export const MutationMask = Placement | Update;
export const Passive = 0b00000000000000010000000000;
+export const LayoutMask = Update;
src\react-reconciler\src\ReactFiberHooks.js
import ReactSharedInternals from "shared/ReactSharedInternals";
import { enqueueConcurrentHookUpdate } from "./ReactFiberConcurrentUpdates";
import { scheduleUpdateOnFiber } from "./ReactFiberWorkLoop";
import is from "shared/objectIs";
+import { Passive as PassiveEffect, Update as UpdateEffect } from "./ReactFiberFlags";
+import { HasEffect as HookHasEffect, Passive as HookPassive, Layout as HookLayout } from "./ReactHookEffectTags";
const { ReactCurrentDispatcher } = ReactSharedInternals;
let currentlyRenderingFiber = null;
let workInProgressHook = null;
let currentHook = null;
const HooksDispatcherOnMountInDEV = {
useReducer: mountReducer,
useState: mountState,
useEffect: mountEffect,
+ useLayoutEffect: mountLayoutEffect,
};
const HooksDispatcherOnUpdateInDEV = {
useReducer: updateReducer,
useState: updateState,
useEffect: updateEffect,
+ useLayoutEffect: updateLayoutEffect,
};
export function useLayoutEffect(reducer, initialArg) {
return ReactCurrentDispatcher.current.useLayoutEffect(reducer, initialArg);
}
+function updateLayoutEffect(create, deps) {
+ return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
+}
+function mountLayoutEffect(create, deps) {
+ const fiberFlags = UpdateEffect;
+ return mountEffectImpl(fiberFlags, HookLayout, create, deps);
+}
+function updateEffect(create, deps) {
+ return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
+}
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, destroy, nextDeps);
}
function areHookInputsEqual(nextDeps, prevDeps) {
if (prevDeps === null) {
return false;
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
function mountEffect(create, deps) {
return mountEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, undefined, nextDeps);
}
function pushEffect(tag, create, destroy, deps) {
const effect = {
tag,
create,
destroy,
deps,
next: null,
};
let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
function createFunctionComponentUpdateQueue() {
return {
lastEffect: null,
};
}
function basicStateReducer(state, action) {
return typeof action === "function" ? action(state) : action;
}
function mountReducer(reducer, initialArg) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialArg;
const queue = {
pending: null,
dispatch: null,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function updateReducer(reducer) {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const current = currentHook;
const pendingQueue = queue.pending;
let baseQueue = null;
let newState = current.memoizedState;
if (pendingQueue !== null) {
baseQueue = pendingQueue;
queue.pending = null;
}
if (baseQueue !== null) {
const first = baseQueue.next;
let update = first;
do {
if (update.hasEagerState) {
newState = update.eagerState;
} else {
const action = update.action;
newState = reducer(newState, action);
}
update = update.next;
} while (update !== null && update !== first);
}
hook.memoizedState = newState;
queue.lastRenderedState = newState;
const dispatch = queue.dispatch;
return [hook.memoizedState, dispatch];
}
function mountState(initialState) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialState;
const queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function dispatchSetState(fiber, queue, action) {
const update = {
action,
hasEagerState: false,
eagerState: null,
next: null,
};
const lastRenderedReducer = queue.lastRenderedReducer;
const currentState = queue.lastRenderedState;
const eagerState = lastRenderedReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
return;
}
const root = enqueueConcurrentHookUpdate(fiber, queue, update);
scheduleUpdateOnFiber(root, fiber);
}
function updateState(initialState) {
return updateReducer(basicStateReducer, initialState);
}
function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function dispatchReducerAction(fiber, queue, action) {
const update = {
action,
next: null,
};
const root = enqueueConcurrentHookUpdate(fiber, queue, update);
scheduleUpdateOnFiber(root, fiber);
}
function updateWorkInProgressHook() {
let nextCurrentHook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
currentHook = nextCurrentHook;
const newHook = {
memoizedState: currentHook.memoizedState,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
export function renderWithHooks(current, workInProgress, Component, props) {
currentlyRenderingFiber = workInProgress;
workInProgress.updateQueue = null;
workInProgress.memoizedState = null;
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
const children = Component(props);
currentlyRenderingFiber = null;
workInProgressHook = null;
currentHook = null;
return children;
}
src\react-reconciler\src\ReactFiberWorkLoop.js
import { scheduleCallback } from "scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
import { completeWork } from "./ReactFiberCompleteWork";
import { MutationMask, NoFlags, Placement, Update, ChildDeletion, Passive } from "./ReactFiberFlags";
import {
commitMutationEffects,
commitPassiveUnmountEffects,
commitPassiveMountEffects,
+ commitLayoutEffects,
} from "./ReactFiberCommitWork";
import { finishQueueingConcurrentUpdates } from "./ReactFiberConcurrentUpdates";
import { FunctionComponent, IndeterminateComponent, HostRoot, HostComponent, HostText } from "./ReactWorkTags";
let workInProgress = null;
let rootDoesHavePassiveEffects = false;
let rootWithPendingPassiveEffects = null;
export function scheduleUpdateOnFiber(root) {
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}
function performConcurrentWorkOnRoot(root) {
renderRootSync(root);
const finishedWork = root.current.alternate;
printFiber(finishedWork);
console.log(`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`);
root.finishedWork = finishedWork;
commitRoot(root);
}
export function flushPassiveEffects() {
if (rootWithPendingPassiveEffects !== null) {
const root = rootWithPendingPassiveEffects;
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);
}
}
function commitRoot(root) {
const { finishedWork } = root;
if ((finishedWork.subtreeFlags & Passive) !== NoFlags || (finishedWork.flags & Passive) !== NoFlags) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(flushPassiveEffects);
}
}
const subtreeHasEffects = (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
commitMutationEffects(finishedWork, root);
+ commitLayoutEffects(finishedWork, root);
root.current = finishedWork;
if (rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
}
}
root.current = finishedWork;
}
function prepareFreshStack(root) {
workInProgress = createWorkInProgress(root.current, null);
finishQueueingConcurrentUpdates();
}
function renderRootSync(root) {
prepareFreshStack(root);
workLoopSync();
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
completeWork(current, completedWork);
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
}
function printFiber(fiber) {
/*
fiber.flags &= ~Forked;
fiber.flags &= ~PlacementDEV;
fiber.flags &= ~Snapshot;
fiber.flags &= ~PerformedWork;
*/
if (fiber.flags !== 0) {
console.log(
getFlags(fiber.flags),
getTag(fiber.tag),
typeof fiber.type === "function" ? fiber.type.name : fiber.type,
fiber.memoizedProps
);
if (fiber.deletions) {
for (let i = 0; i < fiber.deletions.length; i++) {
const childToDelete = fiber.deletions[i];
console.log(getTag(childToDelete.tag), childToDelete.type, childToDelete.memoizedProps);
}
}
}
let child = fiber.child;
while (child) {
printFiber(child);
child = child.sibling;
}
}
function getTag(tag) {
switch (tag) {
case FunctionComponent:
return `FunctionComponent`;
case HostRoot:
return `HostRoot`;
case HostComponent:
return `HostComponent`;
case HostText:
return HostText;
default:
return tag;
}
}
function getFlags(flags) {
if (flags === (Update | Placement | ChildDeletion)) {
return `自己移动和子元素有删除`;
}
if (flags === (ChildDeletion | Update)) {
return `自己有更新和子元素有删除`;
}
if (flags === ChildDeletion) {
return `子元素有删除`;
}
if (flags === (Placement | Update)) {
return `移动并更新`;
}
if (flags === Placement) {
return `插入`;
}
if (flags === Update) {
return `更新`;
}
return flags;
}
src\react-reconciler\src\ReactFiberCommitWork.js
import { HostRoot, HostComponent, HostText, FunctionComponent } from "./ReactWorkTags";
+import { Passive, MutationMask, Placement, Update, LayoutMask } from "./ReactFiberFlags";
import { insertBefore, appendChild, commitUpdate, removeChild } from "react-dom-bindings/src/client/ReactDOMHostConfig";
+import { HasEffect as HookHasEffect, Passive as HookPassive, Layout as HookLayout } from "./ReactHookEffectTags";
export function commitMutationEffects(finishedWork, root) {
commitMutationEffectsOnFiber(finishedWork, root);
}
export function commitPassiveUnmountEffects(finishedWork) {
commitPassiveUnmountOnFiber(finishedWork);
}
function commitPassiveUnmountOnFiber(finishedWork) {
switch (finishedWork.tag) {
case FunctionComponent: {
recursivelyTraversePassiveUnmountEffects(finishedWork);
if (finishedWork.flags & Passive) {
commitHookPassiveUnmountEffects(finishedWork, finishedWork.return, HookPassive | HookHasEffect);
}
break;
}
default: {
recursivelyTraversePassiveUnmountEffects(finishedWork);
break;
}
}
}
function recursivelyTraversePassiveUnmountEffects(parentFiber) {
if (parentFiber.subtreeFlags & Passive) {
let child = parentFiber.child;
while (child !== null) {
commitPassiveUnmountOnFiber(child);
child = child.sibling;
}
}
}
function commitHookPassiveUnmountEffects(finishedWork, nearestMountedAncestor, hookFlags) {
commitHookEffectListUnmount(hookFlags, finishedWork, nearestMountedAncestor);
}
function commitHookEffectListUnmount(flags, finishedWork) {
const updateQueue = finishedWork.updateQueue;
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
destroy();
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
export function commitPassiveMountEffects(root, finishedWork) {
commitPassiveMountOnFiber(root, finishedWork);
}
function commitPassiveMountOnFiber(finishedRoot, finishedWork) {
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent: {
recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork);
if (flags & Passive) {
commitHookPassiveMountEffects(finishedWork, HookPassive | HookHasEffect);
}
break;
}
case HostRoot: {
recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork);
break;
}
default:
break;
}
}
function commitHookPassiveMountEffects(finishedWork, hookFlags) {
commitHookEffectListMount(hookFlags, finishedWork);
}
function commitHookEffectListMount(flags, finishedWork) {
const updateQueue = finishedWork.updateQueue;
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
const create = effect.create;
effect.destroy = create();
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
function recursivelyTraversePassiveMountEffects(root, parentFiber) {
if (parentFiber.subtreeFlags & Passive) {
let child = parentFiber.child;
while (child !== null) {
commitPassiveMountOnFiber(root, child);
child = child.sibling;
}
}
}
let hostParent = null;
function commitDeletionEffects(root, returnFiber, deletedFiber) {
let parent = returnFiber;
findParent: while (parent !== null) {
switch (parent.tag) {
case HostComponent: {
hostParent = parent.stateNode;
break findParent;
}
case HostRoot: {
hostParent = parent.stateNode.containerInfo;
break findParent;
}
default:
break;
}
parent = parent.return;
}
commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
hostParent = null;
}
function commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, deletedFiber) {
switch (deletedFiber.tag) {
case HostComponent:
case HostText: {
recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
if (hostParent !== null) {
removeChild(hostParent, deletedFiber.stateNode);
}
break;
}
default:
break;
}
}
function recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, parent) {
let child = parent.child;
while (child !== null) {
commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);
child = child.sibling;
}
}
function recursivelyTraverseMutationEffects(root, parentFiber) {
const deletions = parentFiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
commitDeletionEffects(root, parentFiber, childToDelete);
}
}
if (parentFiber.subtreeFlags & MutationMask) {
let { child } = parentFiber;
while (child !== null) {
commitMutationEffectsOnFiber(child, root);
child = child.sibling;
}
}
}
function isHostParent(fiber) {
return fiber.tag === HostComponent || fiber.tag === HostRoot;
}
function getHostParentFiber(fiber) {
let parent = fiber.return;
while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}
parent = parent.return;
}
return parent;
}
function insertOrAppendPlacementNode(node, before, parent) {
const { tag } = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost) {
const { stateNode } = node;
if (before) {
insertBefore(parent, stateNode, before);
} else {
appendChild(parent, stateNode);
}
} else {
const { child } = node;
if (child !== null) {
insertOrAppendPlacementNode(child, before, parent);
let { sibling } = child;
while (sibling !== null) {
insertOrAppendPlacementNode(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}
function getHostSibling(fiber) {
let node = fiber;
siblings: while (true) {
// 如果我们没有找到任何东西,让我们试试下一个弟弟
while (node.sibling === null) {
if (node.return === null || isHostParent(node.return)) {
// 如果我们是根Fiber或者父亲是原生节点,我们就是最后的弟弟
return null;
}
node = node.return;
}
// node.sibling.return = node.return
node = node.sibling;
while (node.tag !== HostComponent && node.tag !== HostText) {
// 如果它不是原生节点,并且,我们可能在其中有一个原生节点
// 试着向下搜索,直到找到为止
if (node.flags & Placement) {
// 如果我们没有孩子,可以试试弟弟
continue siblings;
} else {
// node.child.return = node
node = node.child;
}
} // Check if this host node is stable or about to be placed.
// 检查此原生节点是否稳定可以放置
if (!(node.flags & Placement)) {
// 找到它了!
return node.stateNode;
}
}
}
function commitPlacement(finishedWork) {
const parentFiber = getHostParentFiber(finishedWork);
switch (parentFiber.tag) {
case HostComponent: {
const parent = parentFiber.stateNode;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
case HostRoot: {
const parent = parentFiber.stateNode.containerInfo;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
default:
break;
}
}
function commitReconciliationEffects(finishedWork) {
const { flags } = finishedWork;
if (flags & Placement) {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}
}
export function commitMutationEffectsOnFiber(finishedWork, root) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case HostRoot: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
case FunctionComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
+ if (flags & Update) {
+ commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork, finishedWork.return);
+ }
break;
}
case HostComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
const instance = finishedWork.stateNode;
if (instance != null) {
const newProps = finishedWork.memoizedProps;
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
const updatePayload = finishedWork.updateQueue;
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(instance, updatePayload, type, oldProps, newProps, finishedWork);
}
}
}
break;
}
case HostText: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
default: {
break;
}
}
}
+export function commitLayoutEffects(finishedWork, root) {
+ const current = finishedWork.alternate;
+ commitLayoutEffectOnFiber(root, current, finishedWork);
+}
+function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork) {
+ const flags = finishedWork.flags;
+ switch (finishedWork.tag) {
+ case FunctionComponent: {
+ recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
+ if (flags & Update) {
+ commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);
+ }
+ break;
+ }
+ case HostRoot: {
+ recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
+ break;
+ }
+ default:
+ break;
+ }
+}
+function recursivelyTraverseLayoutEffects(root, parentFiber) {
+ if (parentFiber.subtreeFlags & LayoutMask) {
+ let child = parentFiber.child;
+ while (child !== null) {
+ const current = child.alternate;
+ commitLayoutEffectOnFiber(root, current, child);
+ child = child.sibling;
+ }
+ }
+}
+function commitHookLayoutEffects(finishedWork, hookFlags) {
+ commitHookEffectListMount(hookFlags, finishedWork);
+}
src\react-reconciler\src\ReactFiberWorkLoop.js
+import { NormalPriority as NormalSchedulerPriority, scheduleCallback as Scheduler_scheduleCallback } from "./Scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
import { completeWork } from "./ReactFiberCompleteWork";
import { MutationMask, NoFlags, Placement, Update, ChildDeletion, Passive } from "./ReactFiberFlags";
import {
commitMutationEffects,
commitPassiveUnmountEffects,
commitPassiveMountEffects,
commitLayoutEffects,
} from "./ReactFiberCommitWork";
import { finishQueueingConcurrentUpdates } from "./ReactFiberConcurrentUpdates";
import { FunctionComponent, IndeterminateComponent, HostRoot, HostComponent, HostText } from "./ReactWorkTags";
let workInProgress = null;
let rootDoesHavePassiveEffects = false;
let rootWithPendingPassiveEffects = null;
export function scheduleUpdateOnFiber(root) {
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
+ Scheduler_scheduleCallback(NormalSchedulerPriority, performConcurrentWorkOnRoot.bind(null, root));
}
function performConcurrentWorkOnRoot(root) {
renderRootSync(root);
const finishedWork = root.current.alternate;
printFiber(finishedWork);
console.log(`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`);
root.finishedWork = finishedWork;
commitRoot(root);
}
export function flushPassiveEffects() {
if (rootWithPendingPassiveEffects !== null) {
const root = rootWithPendingPassiveEffects;
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);
}
}
function commitRoot(root) {
const { finishedWork } = root;
if ((finishedWork.subtreeFlags & Passive) !== NoFlags || (finishedWork.flags & Passive) !== NoFlags) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
+ Scheduler_scheduleCallback(NormalSchedulerPriority, flushPassiveEffects);
}
}
const subtreeHasEffects = (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
commitMutationEffects(finishedWork, root);
commitLayoutEffects(finishedWork, root);
root.current = finishedWork;
if (rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
}
}
root.current = finishedWork;
}
function prepareFreshStack(root) {
workInProgress = createWorkInProgress(root.current, null);
finishQueueingConcurrentUpdates();
}
function renderRootSync(root) {
prepareFreshStack(root);
workLoopSync();
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
completeWork(current, completedWork);
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
}
function printFiber(fiber) {
/*
fiber.flags &= ~Forked;
fiber.flags &= ~PlacementDEV;
fiber.flags &= ~Snapshot;
fiber.flags &= ~PerformedWork;
*/
if (fiber.flags !== 0) {
console.log(
getFlags(fiber.flags),
getTag(fiber.tag),
typeof fiber.type === "function" ? fiber.type.name : fiber.type,
fiber.memoizedProps
);
if (fiber.deletions) {
for (let i = 0; i < fiber.deletions.length; i++) {
const childToDelete = fiber.deletions[i];
console.log(getTag(childToDelete.tag), childToDelete.type, childToDelete.memoizedProps);
}
}
}
let child = fiber.child;
while (child) {
printFiber(child);
child = child.sibling;
}
}
function getTag(tag) {
switch (tag) {
case FunctionComponent:
return `FunctionComponent`;
case HostRoot:
return `HostRoot`;
case HostComponent:
return `HostComponent`;
case HostText:
return HostText;
default:
return tag;
}
}
function getFlags(flags) {
if (flags === (Update | Placement | ChildDeletion)) {
return `自己移动和子元素有删除`;
}
if (flags === (ChildDeletion | Update)) {
return `自己有更新和子元素有删除`;
}
if (flags === ChildDeletion) {
return `子元素有删除`;
}
if (flags === (Placement | Update)) {
return `移动并更新`;
}
if (flags === Placement) {
return `插入`;
}
if (flags === Update) {
return `更新`;
}
return flags;
}
src\react-reconciler\src\Scheduler.js
import * as Scheduler from "scheduler";
export const scheduleCallback = Scheduler.unstable_scheduleCallback;
export const NormalPriority = Scheduler.unstable_NormalPriority;
src\scheduler\src\forks\Scheduler.js
import {
ImmediatePriority,
UserBlockingPriority,
NormalPriority,
LowPriority,
IdlePriority,
} from "../SchedulerPriorities";
import { push, pop, peek } from "../SchedulerMinHeap";
import { frameYieldMs } from "../SchedulerFeatureFlags";
const maxSigned31BitInt = 1073741823;
const IMMEDIATE_PRIORITY_TIMEOUT = -1;
const USER_BLOCKING_PRIORITY_TIMEOUT = 250;
const NORMAL_PRIORITY_TIMEOUT = 5000;
const LOW_PRIORITY_TIMEOUT = 10000;
const IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;
const taskQueue = [];
let taskIdCounter = 1;
let scheduledHostCallback = null;
let startTime = -1;
let currentTask = null;
const frameInterval = frameYieldMs;
const channel = new MessageChannel();
const port = channel.port2;
const getCurrentTime = () => performance.now();
channel.port1.onmessage = performWorkUntilDeadline;
function schedulePerformWorkUntilDeadline() {
port.postMessage(null);
}
function performWorkUntilDeadline() {
if (scheduledHostCallback !== null) {
startTime = getCurrentTime();
let hasMoreWork = true;
try {
hasMoreWork = scheduledHostCallback(startTime);
} finally {
if (hasMoreWork) {
schedulePerformWorkUntilDeadline();
} else {
scheduledHostCallback = null;
}
}
}
}
function requestHostCallback(callback) {
scheduledHostCallback = callback;
schedulePerformWorkUntilDeadline();
}
function unstable_scheduleCallback(priorityLevel, callback) {
const currentTime = getCurrentTime();
const startTime = currentTime;
let timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT;
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT;
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
}
const expirationTime = startTime + timeout;
const newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
requestHostCallback(flushWork);
return newTask;
}
function flushWork(initialTime) {
return workLoop(initialTime);
}
function shouldYieldToHost() {
const timeElapsed = getCurrentTime() - startTime;
if (timeElapsed < frameInterval) {
return false;
}
return true;
}
function workLoop(initialTime) {
let currentTime = initialTime;
currentTask = peek(taskQueue);
while (currentTask !== null) {
if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
break;
}
const callback = currentTask.callback;
if (typeof callback === "function") {
currentTask.callback = null;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === "function") {
currentTask.callback = continuationCallback;
return true;
}
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
} else {
pop(taskQueue);
}
currentTask = peek(taskQueue);
}
if (currentTask !== null) {
return true;
}
return false;
}
export { NormalPriority as unstable_NormalPriority, unstable_scheduleCallback };
src\scheduler\src\SchedulerFeatureFlags.js
export const frameYieldMs = 5;
src\scheduler\src\SchedulerMinHeap.js
export function push(heap, node) {
const index = heap.length;
heap.push(node);
siftUp(heap, node, index);
}
export function peek(heap) {
return heap.length === 0 ? null : heap[0];
}
export function pop(heap) {
if (heap.length === 0) {
return null;
}
const first = heap[0];
const last = heap.pop();
if (last !== first) {
heap[0] = last;
siftDown(heap, last, 0);
}
return first;
}
function siftUp(heap, node, i) {
let index = i;
while (index > 0) {
const parentIndex = (index - 1) >>> 1;
const parent = heap[parentIndex];
if (compare(parent, node) > 0) {
heap[parentIndex] = node;
heap[index] = parent;
index = parentIndex;
} else {
return;
}
}
}
function siftDown(heap, node, i) {
let index = i;
const length = heap.length;
const halfLength = length >>> 1;
while (index < halfLength) {
const leftIndex = (index + 1) * 2 - 1;
const left = heap[leftIndex];
const rightIndex = leftIndex + 1;
const right = heap[rightIndex];
if (compare(left, node) < 0) {
if (rightIndex < length && compare(right, left) < 0) {
heap[index] = right;
heap[rightIndex] = node;
index = rightIndex;
} else {
heap[index] = left;
heap[leftIndex] = node;
index = leftIndex;
}
} else if (rightIndex < length && compare(right, node) < 0) {
heap[index] = right;
heap[rightIndex] = node;
index = rightIndex;
} else {
return;
}
}
}
function compare(a, b) {
const diff = a.sortIndex - b.sortIndex;
return diff !== 0 ? diff : a.id - b.id;
}
src\scheduler\src\SchedulerPriorities.js
export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
+let element = <h1>hello</h1>;
const root = createRoot(document.getElementById("root"));
root.render(element);
src\react-reconciler\src\ReactFiberReconciler.js
import { createFiberRoot } from "./ReactFiberRoot";
import { createUpdate, enqueueUpdate } from "./ReactFiberClassUpdateQueue";
+import { scheduleUpdateOnFiber, requestUpdateLane } from "./ReactFiberWorkLoop";
export function createContainer(containerInfo) {
return createFiberRoot(containerInfo);
}
export function updateContainer(element, container) {
const current = container.current;
+ const lane = requestUpdateLane(current);
+ const update = createUpdate(lane);
update.payload = { element };
+ const root = enqueueUpdate(current, update, lane);
+ scheduleUpdateOnFiber(root, current, lane);
}
src\react-reconciler\src\ReactFiberClassUpdateQueue.js
import assign from "shared/assign";
+import { enqueueConcurrentClassUpdate } from './ReactFiberConcurrentUpdates'
+import { mergeLanes, NoLanes, NoLane, isSubsetOfLanes } from './ReactFiberLane';
export const UpdateState = 0;
export function initializeUpdateQueue(fiber) {
const queue = {
+ baseState: fiber.memoizedState,
+ firstBaseUpdate: null,
+ lastBaseUpdate: null,
shared: {
pending: null,
},
};
fiber.updateQueue = queue;
}
+export function createUpdate(lane) {
+ const update = { tag: UpdateState, lane, next: null };
return update;
}
+export function enqueueUpdate(fiber, update, lane) {
+ const updateQueue = fiber.updateQueue
+ const sharedQueue = updateQueue.shared
+ return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
+}
function getStateFromUpdate(update, prevState, nextProps) {
switch (update.tag) {
case UpdateState: {
const { payload } = update;
+ let partialState;
+ if (typeof payload === 'function') {
+ partialState = payload.call(null, prevState, nextProps);
+ } else {
+ partialState = payload;
+ }
+ return assign({}, prevState, partialState);
}
default:
return prevState;
}
}
+export const processUpdateQueue = (workInProgress, props, workInProgressRootRenderLanes) => {
+ // 获取新的更新队列
+ const queue = workInProgress.updateQueue
+ // 第一个跳过的更新
+ let firstBaseUpdate = queue.firstBaseUpdate;
+ // 最后一个跳过的更新
+ let lastBaseUpdate = queue.lastBaseUpdate;
+ // 获取待生效的队列
+ const pendingQueue = queue.shared.pending
+ /** 如果有新链表合并新旧链表开始 */
+ // 如果有新的待生效的队列
+ if (pendingQueue !== null) {
+ // 先清空待生效的队列
+ queue.shared.pending = null
+ // 最后一个待生效的更新
+ const lastPendingUpdate = pendingQueue
+ // 第一个待生效的更新
+ const firstPendingUpdate = lastPendingUpdate.next
+ // 把环状链表剪开
+ lastPendingUpdate.next = null
+ // 如果没有老的更新队列
+ if (lastBaseUpdate === null) {
+ // 第一个基本更新就是待生效队列的第一个更新
+ firstBaseUpdate = firstPendingUpdate;
+ } else {
+ // 否则把待生效更新队列添加到基本更新的尾部
+ lastBaseUpdate.next = firstPendingUpdate;
+ }
+ // 最后一个基本更新肯定就是最后一个待生效的更新
+ lastBaseUpdate = lastPendingUpdate;
+ /** 合并新旧链表结束 */
+ }
+
+ // 如果有更新
+ if (firstBaseUpdate !== null) {
+ // 基本状态
+ let newState = queue.baseState;
+ // 新的车道
+ let newLanes = NoLanes;
+ // 新的基本状态
+ let newBaseState = null;
+ // 新的第一个基本更新
+ let newFirstBaseUpdate = null;
+ // 新的最后一个基本更新
+ let newLastBaseUpdate = null;
+ // 第一个更新
+ let update = firstBaseUpdate;
+ do {
+ const updateLane = update.lane;
+ const shouldSkipUpdate = !isSubsetOfLanes(workInProgressRootRenderLanes, updateLane);
+ // 判断优先级是否足够,如果不够就跳过此更新
+ if (shouldSkipUpdate) {
+ // 复制一个新的更新并添加新的基本链表中
+ const clone = {
+ lane: updateLane,
+ tag: update.tag,
+ payload: update.payload,
+ next: null
+ };
+ if (newLastBaseUpdate === null) {
+ newFirstBaseUpdate = newLastBaseUpdate = clone;
+ newBaseState = newState;
+ } else {
+ newLastBaseUpdate = newLastBaseUpdate.next = clone;
+ }
+ // 保存此fiber上还剩下的更新车道
+ newLanes = mergeLanes(newLanes, updateLane);
+ } else {
+ // 如果已经有跳过的更新了,即使优先级足够也需要添到新的基本链表中
+ if (newLastBaseUpdate !== null) {
+ const clone = {
+ lane: NoLane,
+ tag: update.tag,
+ payload: update.payload,
+ next: null
+ };
+ newLastBaseUpdate = newLastBaseUpdate.next = clone;
+ }
+ // 根据更新计算新状态
+ newState = getStateFromUpdate(update, newState, props);
+ update = update.next;
+ }
+ } while (update);
+ // 如果没有跳过的更新
+ if (newLastBaseUpdate === null) {
+ newBaseState = newState;
+ }
+ queue.baseState = newBaseState;
+ queue.firstBaseUpdate = newFirstBaseUpdate;
+ queue.lastBaseUpdate = newLastBaseUpdate;
+ workInProgress.lanes = newLanes;
+ workInProgress.memoizedState = newState;
+ }
+}
+export function cloneUpdateQueue(current, workInProgress) {
+ const queue = workInProgress.updateQueue;
+ const currentQueue = current.updateQueue;
+ if (queue === currentQueue) {
+ const clone = {
+ baseState: currentQueue.baseState,
+ firstBaseUpdate: currentQueue.firstBaseUpdate,
+ lastBaseUpdate: currentQueue.lastBaseUpdate,
+ shared: currentQueue.shared,
+ };
+ workInProgress.updateQueue = clone;
+ }
+}
src\react-reconciler\src\ReactFiberConcurrentUpdates.js
import { HostRoot } from "./ReactWorkTags";
const concurrentQueues = [];
let concurrentQueuesIndex = 0;
export function markUpdateLaneFromFiberToRoot(sourceFiber) {
let node = sourceFiber;
let parent = sourceFiber.return;
while (parent !== null) {
node = parent;
parent = parent.return;
}
if (node.tag === HostRoot) {
const root = node.stateNode;
return root;
}
return null;
}
export function enqueueConcurrentHookUpdate(fiber, queue, update) {
enqueueUpdate(fiber, queue, update);
return getRootForUpdatedFiber(fiber);
}
+export function enqueueConcurrentClassUpdate(fiber, queue, update, lane) {
+ enqueueUpdate(fiber, queue, update, lane);
+ return getRootForUpdatedFiber(fiber);
+}
+function enqueueUpdate(fiber, queue, update, lane) {
concurrentQueues[concurrentQueuesIndex++] = fiber;
concurrentQueues[concurrentQueuesIndex++] = queue;
concurrentQueues[concurrentQueuesIndex++] = update;
+ concurrentQueues[concurrentQueuesIndex++] = lane;
}
function getRootForUpdatedFiber(sourceFiber) {
let node = sourceFiber;
let parent = node.return;
while (parent !== null) {
node = parent;
parent = node.return;
}
return node.tag === HostRoot ? node.stateNode : null;
}
export function finishQueueingConcurrentUpdates() {
const endIndex = concurrentQueuesIndex;
concurrentQueuesIndex = 0;
let i = 0;
while (i < endIndex) {
const fiber = concurrentQueues[i++];
const queue = concurrentQueues[i++];
const update = concurrentQueues[i++];
+ const lane = concurrentQueues[i++]
if (queue !== null && update !== null) {
const pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
}
}
src\react-reconciler\src\ReactFiberWorkLoop.js
+import {
+ scheduleCallback as Scheduler_scheduleCallback,
+ ImmediatePriority as ImmediateSchedulerPriority,
+ UserBlockingPriority as UserBlockingSchedulerPriority,
+ NormalPriority as NormalSchedulerPriority,
+ IdlePriority as IdleSchedulerPriority,
+} from "./Scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
import { completeWork } from "./ReactFiberCompleteWork";
+import { MutationMask, NoFlags, Passive } from "./ReactFiberFlags";
import {
commitMutationEffects,
commitPassiveUnmountEffects,
commitPassiveMountEffects,
commitLayoutEffects,
} from "./ReactFiberCommitWork";
import { finishQueueingConcurrentUpdates } from "./ReactFiberConcurrentUpdates";
+import {
+ NoLane, markRootUpdated, NoLanes,
+ getNextLanes, getHighestPriorityLane, SyncLane,
+ includesBlockingLane
+} from './ReactFiberLane';
+import {
+ getCurrentUpdatePriority, lanesToEventPriority, DiscreteEventPriority, ContinuousEventPriority,
+ DefaultEventPriority, IdleEventPriority,
+} from './ReactEventPriorities';
+import { getCurrentEventPriority } from 'react-dom-bindings/src/client/ReactDOMHostConfig';
let workInProgress = null;
let rootDoesHavePassiveEffects = false;
let rootWithPendingPassiveEffects = null;
+let workInProgressRootRenderLanes = NoLanes;
+export function scheduleUpdateOnFiber(root, fiber, lane) {
+ markRootUpdated(root, lane);
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
+ const nextLanes = getNextLanes(root, NoLanes);
+ const newCallbackPriority = getHighestPriorityLane(nextLanes);
+ if (newCallbackPriority === SyncLane) {
+ // TODO
+ } else {
+ let schedulerPriorityLevel;
+ switch (lanesToEventPriority(nextLanes)) {
+ case DiscreteEventPriority:
+ schedulerPriorityLevel = ImmediateSchedulerPriority;
+ break;
+ case ContinuousEventPriority:
+ schedulerPriorityLevel = UserBlockingSchedulerPriority;
+ break;
+ case DefaultEventPriority:
+ schedulerPriorityLevel = NormalSchedulerPriority;
+ break;
+ case IdleEventPriority:
+ schedulerPriorityLevel = IdleSchedulerPriority;
+ break;
+ default:
+ schedulerPriorityLevel = NormalSchedulerPriority;
+ break;
+ }
+ Scheduler_scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root))
}
}
+function performConcurrentWorkOnRoot(root,didTimeout) {
+ const lanes = getNextLanes(root, NoLanes);
+ if (lanes === NoLanes) {
+ return null;
+ }
+ const shouldTimeSlice = !includesBlockingLane(root, lanes) && (!didTimeout);
+ if (shouldTimeSlice) {
+ renderRootConcurrent(root, lanes)
+ } else {
+ renderRootSync(root, lanes);
+ }
const finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
commitRoot(root);
}
+function renderRootConcurrent(root, lanes) {
+ console.log(root, lanes);
+}
export function flushPassiveEffects() {
if (rootWithPendingPassiveEffects !== null) {
const root = rootWithPendingPassiveEffects;
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);
}
}
function commitRoot(root) {
const { finishedWork } = root;
if ((finishedWork.subtreeFlags & Passive) !== NoFlags || (finishedWork.flags & Passive) !== NoFlags) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
Scheduler_scheduleCallback(NormalSchedulerPriority, flushPassiveEffects);
}
}
const subtreeHasEffects = (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
commitMutationEffects(finishedWork, root);
commitLayoutEffects(finishedWork, root);
root.current = finishedWork;
if (rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
}
}
root.current = finishedWork;
}
+function prepareFreshStack(root, lanes) {
workInProgress = createWorkInProgress(root.current, null);
+ workInProgressRootRenderLanes = lanes;
finishQueueingConcurrentUpdates();
}
+function renderRootSync(root, lanes) {
+ prepareFreshStack(root, lanes);
workLoopSync();
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
+ const next = beginWork(current, unitOfWork, workInProgressRootRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
completeWork(current, completedWork);
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
}
+export function requestUpdateLane() {
+ const updateLane = getCurrentUpdatePriority();
+ if (updateLane !== NoLane) {
+ return updateLane;
+ }
+ const eventLane = getCurrentEventPriority();
+ return eventLane;
+}
src\react-reconciler\src\ReactFiberBeginWork.js
import { HostRoot, HostComponent, HostText, IndeterminateComponent, FunctionComponent } from "./ReactWorkTags";
+import { processUpdateQueue, cloneUpdateQueue } from "./ReactFiberClassUpdateQueue";
import { mountChildFibers, reconcileChildFibers } from "./ReactChildFiber";
import { shouldSetTextContent } from "react-dom-bindings/src/client/ReactDOMHostConfig";
import { renderWithHooks } from "react-reconciler/src/ReactFiberHooks";
function reconcileChildren(current, workInProgress, nextChildren) {
if (current === null) {
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren);
} else {
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren);
}
}
+function updateHostRoot(current, workInProgress, renderLanes) {
+ const nextProps = workInProgress.pendingProps;
+ cloneUpdateQueue(current, workInProgress);
+ processUpdateQueue(workInProgress, nextProps, renderLanes)
const nextState = workInProgress.memoizedState;
const nextChildren = nextState.element;
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
function updateHostComponent(current, workInProgress) {
const { type } = workInProgress;
const nextProps = workInProgress.pendingProps;
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
nextChildren = null;
}
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
function mountIndeterminateComponent(_current, workInProgress, Component) {
const props = workInProgress.pendingProps;
const value = renderWithHooks(null, workInProgress, Component, props);
workInProgress.tag = FunctionComponent;
reconcileChildren(null, workInProgress, value);
return workInProgress.child;
}
function updateFunctionComponent(current, workInProgress, Component, nextProps,workInProgressRootRenderLanes) {
const nextChildren = renderWithHooks(current, workInProgress, Component, nextProps,workInProgressRootRenderLanes);
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
+export function beginWork(current, workInProgress, renderLanes) {
switch (workInProgress.tag) {
case IndeterminateComponent: {
+ return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
}
case FunctionComponent: {
const Component = workInProgress.type;
const resolvedProps = workInProgress.pendingProps;
+ return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderLanes);
}
case HostRoot:
+ return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
+ return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
default:
return null;
}
}
src\react-reconciler\src\ReactFiberLane.js
export const TotalLanes = 31;
export const NoLanes = 0b0000000000000000000000000000000;
export const NoLane = 0b0000000000000000000000000000000;
export const SyncLane = 0b0000000000000000000000000000001;// 1
export const InputContinuousLane = 0b0000000000000000000000000000100;// 4
export const DefaultLane = 0b0000000000000000000000000010000;// 16
export const NonIdleLanes = 0b0001111111111111111111111111111;
export const IdleLane = 0b0100000000000000000000000000000;
export function mergeLanes(a, b) {
return a | b;
}
export function markRootUpdated(root, updateLane) {
root.pendingLanes |= updateLane;
}
export function getNextLanes(root) {
const pendingLanes = root.pendingLanes;
if (pendingLanes === NoLanes) {
return NoLanes;
}
const nextLanes = getHighestPriorityLanes(pendingLanes);
return nextLanes;
}
function getHighestPriorityLanes(lanes) {
return getHighestPriorityLane(lanes);
}
export function getHighestPriorityLane(lanes) {
return lanes & -lanes;
}
export function includesNonIdleWork(lanes) {
return (lanes & NonIdleLanes) !== NoLanes;
}
export function includesBlockingLane(root, lanes) {
const SyncDefaultLanes = InputContinuousLane | DefaultLane;
return (lanes & SyncDefaultLanes) !== NoLanes;
}
export function isSubsetOfLanes(set, subset) {
return (set & subset) === subset;
}
src\react-reconciler\src\ReactEventPriorities.js
import {
NoLane, DefaultLane, getHighestPriorityLane,
includesNonIdleWork, SyncLane, InputContinuousLane, IdleLane
} from './ReactFiberLane';
export const DefaultEventPriority = DefaultLane;
export const DiscreteEventPriority = SyncLane;
export const ContinuousEventPriority = InputContinuousLane;
export const IdleEventPriority = IdleLane;
let currentUpdatePriority = NoLane;
export function getCurrentUpdatePriority() {
return currentUpdatePriority;
}
export function setCurrentUpdatePriority(newPriority) {
currentUpdatePriority = newPriority;
}
export function isHigherEventPriority(a, b) {
return a !== 0 && a < b;
}
export function lanesToEventPriority(lanes) {
const lane = getHighestPriorityLane(lanes);
if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
return DiscreteEventPriority;
}
if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
return ContinuousEventPriority;
}
if (includesNonIdleWork(lane)) {
return DefaultEventPriority;
}
return IdleEventPriority;
}
src\react-dom-bindings\src\client\ReactDOMHostConfig.js
import { setInitialProperties, diffProperties, updateProperties } from "./ReactDOMComponent";
import { precacheFiberNode, updateFiberProps } from "./ReactDOMComponentTree";
+import { getEventPriority } from '../events/ReactDOMEventListener';
+import { DefaultEventPriority } from 'react-reconciler/src/ReactEventPriorities';
export function shouldSetTextContent(type, props) {
return typeof props.children === "string" || typeof props.children === "number";
}
export const appendInitialChild = (parent, child) => {
parent.appendChild(child);
};
export const createInstance = (type, props, internalInstanceHandle) => {
const domElement = document.createElement(type);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
};
export const createTextInstance = (content) => document.createTextNode(content);
export function finalizeInitialChildren(domElement, type, props) {
setInitialProperties(domElement, type, props);
}
export function appendChild(parentInstance, child) {
parentInstance.appendChild(child);
}
export function insertBefore(parentInstance, child, beforeChild) {
parentInstance.insertBefore(child, beforeChild);
}
export function prepareUpdate(domElement, type, oldProps, newProps) {
return diffProperties(domElement, type, oldProps, newProps);
}
export function commitUpdate(domElement, updatePayload, type, oldProps, newProps) {
updateProperties(domElement, updatePayload, type, oldProps, newProps);
updateFiberProps(domElement, newProps);
}
export function removeChild(parentInstance, child) {
parentInstance.removeChild(child);
}
+export function getCurrentEventPriority() {
+ const currentEvent = window.event;
+ if (currentEvent === undefined) {
+ return DefaultEventPriority;
+ }
+ return getEventPriority(currentEvent.type);
+}
src\react-dom-bindings\src\events\ReactDOMEventListener.js
import getEventTarget from "./getEventTarget";
import { getClosestInstanceFromNode } from "../client/ReactDOMComponentTree";
import { dispatchEventForPluginEventSystem } from "./DOMPluginEventSystem";
+import { DiscreteEventPriority, ContinuousEventPriority, DefaultEventPriority } from 'react-reconciler/src/ReactEventPriorities';
export function createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags
) {
const listenerWrapper = dispatchDiscreteEvent;
return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer);
}
function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent) {
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
}
export function dispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) {
const nativeEventTarget = getEventTarget(nativeEvent);
const targetInst = getClosestInstanceFromNode(nativeEventTarget);
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer
);
}
+export function getEventPriority(domEventName) {
+ switch (domEventName) {
+ case 'click':
+ return DiscreteEventPriority;
+ case 'drag':
+ return ContinuousEventPriority;
+ default:
+ return DefaultEventPriority;
+ }
+}
src\react-reconciler\src\Scheduler.js
import * as Scheduler from 'scheduler'
export const scheduleCallback = Scheduler.unstable_scheduleCallback
export const NormalPriority = Scheduler.unstable_NormalPriority
+export const ImmediatePriority = Scheduler.unstable_ImmediatePriority;
+export const UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
+export const LowPriority = Scheduler.unstable_LowPriority;
+export const IdlePriority = Scheduler.unstable_IdlePriority;
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
+function FunctionComponent() {
+ const [number, setNumber] = React.useState(0);
+ return <button onClick={() => {
+ setNumber(number + 1)
+ }}>{number}</button>
+}
const element = <FunctionComponent />;
const container = document.getElementById("root");
const root = createRoot(container);
root.render(element);
src\react-reconciler\src\ReactFiberHooks.js
import ReactSharedInternals from "shared/ReactSharedInternals";
import { enqueueConcurrentHookUpdate } from "./ReactFiberConcurrentUpdates";
+import { scheduleUpdateOnFiber, requestUpdateLane } from "./ReactFiberWorkLoop";
import is from "shared/objectIs";
import { Passive as PassiveEffect, Update as UpdateEffect } from "./ReactFiberFlags";
import { HasEffect as HookHasEffect, Passive as HookPassive, Layout as HookLayout } from "./ReactHookEffectTags";
const { ReactCurrentDispatcher } = ReactSharedInternals;
let currentlyRenderingFiber = null;
let workInProgressHook = null;
let currentHook = null;
const HooksDispatcherOnMountInDEV = {
useReducer: mountReducer,
useState: mountState,
useEffect: mountEffect,
useLayoutEffect: mountLayoutEffect,
};
const HooksDispatcherOnUpdateInDEV = {
useReducer: updateReducer,
useState: updateState,
useEffect: updateEffect,
useLayoutEffect: updateLayoutEffect,
};
export function useLayoutEffect(reducer, initialArg) {
return ReactCurrentDispatcher.current.useLayoutEffect(reducer, initialArg);
}
function updateLayoutEffect(create, deps) {
return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}
function mountLayoutEffect(create, deps) {
const fiberFlags = UpdateEffect;
return mountEffectImpl(fiberFlags, HookLayout, create, deps);
}
function updateEffect(create, deps) {
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, destroy, nextDeps);
}
function areHookInputsEqual(nextDeps, prevDeps) {
if (prevDeps === null) {
return false;
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
function mountEffect(create, deps) {
return mountEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, undefined, nextDeps);
}
function pushEffect(tag, create, destroy, deps) {
const effect = {
tag,
create,
destroy,
deps,
next: null,
};
let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
function createFunctionComponentUpdateQueue() {
return {
lastEffect: null,
};
}
function basicStateReducer(state, action) {
return typeof action === "function" ? action(state) : action;
}
function mountReducer(reducer, initialArg) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialArg;
const queue = {
pending: null,
dispatch: null,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function updateReducer(reducer) {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const current = currentHook;
const pendingQueue = queue.pending;
let baseQueue = null;
let newState = current.memoizedState;
if (pendingQueue !== null) {
baseQueue = pendingQueue;
queue.pending = null;
}
if (baseQueue !== null) {
const first = baseQueue.next;
let update = first;
do {
if (update.hasEagerState) {
newState = update.eagerState;
} else {
const action = update.action;
newState = reducer(newState, action);
}
update = update.next;
} while (update !== null && update !== first);
}
hook.memoizedState = newState;
queue.lastRenderedState = newState;
const dispatch = queue.dispatch;
return [hook.memoizedState, dispatch];
}
function mountState(initialState) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialState;
const queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function dispatchSetState(fiber, queue, action) {
+ const lane = requestUpdateLane(fiber);
const update = {
+ lane,
action,
hasEagerState: false,
eagerState: null,
next: null,
};
const lastRenderedReducer = queue.lastRenderedReducer;
const currentState = queue.lastRenderedState;
const eagerState = lastRenderedReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
return;
}
+ const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
+ scheduleUpdateOnFiber(root, fiber, lane);
}
function updateState(initialState) {
return updateReducer(basicStateReducer, initialState);
}
function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function dispatchReducerAction(fiber, queue, action) {
const update = {
action,
next: null,
};
const root = enqueueConcurrentHookUpdate(fiber, queue, update);
scheduleUpdateOnFiber(root, fiber);
}
function updateWorkInProgressHook() {
let nextCurrentHook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
currentHook = nextCurrentHook;
const newHook = {
memoizedState: currentHook.memoizedState,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
export function renderWithHooks(current, workInProgress, Component, props) {
currentlyRenderingFiber = workInProgress;
workInProgress.updateQueue = null;
workInProgress.memoizedState = null;
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
const children = Component(props);
currentlyRenderingFiber = null;
workInProgressHook = null;
currentHook = null;
return children;
}
src\react-reconciler\src\ReactFiberWorkLoop.js
import {
scheduleCallback as Scheduler_scheduleCallback,
ImmediatePriority as ImmediateSchedulerPriority,
UserBlockingPriority as UserBlockingSchedulerPriority,
NormalPriority as NormalSchedulerPriority,
IdlePriority as IdleSchedulerPriority,
} from "./Scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
import { completeWork } from "./ReactFiberCompleteWork";
import { MutationMask, NoFlags, Passive } from "./ReactFiberFlags";
import {
commitMutationEffects,
commitPassiveUnmountEffects,
commitPassiveMountEffects,
commitLayoutEffects,
} from "./ReactFiberCommitWork";
import { finishQueueingConcurrentUpdates } from "./ReactFiberConcurrentUpdates";
import {
NoLane, markRootUpdated, NoLanes,
getNextLanes, getHighestPriorityLane, SyncLane,
includesBlockingLane
} from './ReactFiberLane';
import {
getCurrentUpdatePriority, lanesToEventPriority, DiscreteEventPriority, ContinuousEventPriority,
+ DefaultEventPriority, IdleEventPriority, setCurrentUpdatePriority
} from './ReactEventPriorities';
import { getCurrentEventPriority } from 'react-dom-bindings/src/client/ReactDOMHostConfig';
+import { scheduleSyncCallback, flushSyncCallbacks } from './ReactFiberSyncTaskQueue';
let workInProgress = null;
let rootDoesHavePassiveEffects = false;
let rootWithPendingPassiveEffects = null;
let workInProgressRootRenderLanes = NoLanes;
export function scheduleUpdateOnFiber(root, fiber, lane) {
markRootUpdated(root, lane);
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
const nextLanes = getNextLanes(root, NoLanes);
const newCallbackPriority = getHighestPriorityLane(nextLanes);
if (newCallbackPriority === SyncLane) {
+ scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
+ queueMicrotask(flushSyncCallbacks);
} else {
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
Scheduler_scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root))
}
}
+function performSyncWorkOnRoot(root) {
+ const lanes = getNextLanes(root, NoLanes);
+ renderRootSync(root, lanes);
+ const finishedWork = root.current.alternate
+ root.finishedWork = finishedWork
+ commitRoot(root)
+ return null;//如果没有任务了一定要返回null
+}
function performConcurrentWorkOnRoot(root) {
const lanes = getNextLanes(root, NoLanes);
if (lanes === NoLanes) {
return null;
}
const shouldTimeSlice = !includesBlockingLane(root, lanes) && (!didTimeout);
if (shouldTimeSlice) {
renderRootConcurrent(root, lanes)
} else {
renderRootSync(root, lanes);
}
const finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
commitRoot(root);
}
function renderRootConcurrent(root, lanes) {
console.log(root, lanes);
}
export function flushPassiveEffects() {
if (rootWithPendingPassiveEffects !== null) {
const root = rootWithPendingPassiveEffects;
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);
}
}
+function commitRoot(root) {
+ const previousPriority = getCurrentUpdatePriority();
+ try {
+ setCurrentUpdatePriority(DiscreteEventPriority);
+ commitRootImpl(root);
+ } finally {
+ setCurrentUpdatePriority(previousPriority);
+ }
+}
+function commitRootImpl(root) {
const { finishedWork } = root;
if ((finishedWork.subtreeFlags & Passive) !== NoFlags || (finishedWork.flags & Passive) !== NoFlags) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
Scheduler_scheduleCallback(NormalSchedulerPriority, flushPassiveEffects);
}
}
const subtreeHasEffects = (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
commitMutationEffects(finishedWork, root);
commitLayoutEffects(finishedWork, root);
root.current = finishedWork;
if (rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
}
}
root.current = finishedWork;
+}
function prepareFreshStack(root, lanes) {
workInProgress = createWorkInProgress(root.current, null);
workInProgressRootRenderLanes = lanes;
finishQueueingConcurrentUpdates();
}
function renderRootSync(root, lanes) {
prepareFreshStack(root, lanes);
workLoopSync();
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork, workInProgressRootRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
completeWork(current, completedWork);
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
}
export function requestUpdateLane() {
const updateLane = getCurrentUpdatePriority();
if (updateLane !== NoLane) {
return updateLane;
}
const eventLane = getCurrentEventPriority();
return eventLane;
}
src\react-reconciler\src\ReactFiberSyncTaskQueue.js
import { DiscreteEventPriority, getCurrentUpdatePriority, setCurrentUpdatePriority } from './ReactEventPriorities';
let syncQueue = null;
let isFlushingSyncQueue = false;
export function scheduleSyncCallback(callback) {
if (syncQueue === null) {
syncQueue = [callback];
} else {
syncQueue.push(callback);
}
}
export function flushSyncCallbacks() {
if (!isFlushingSyncQueue && syncQueue !== null) {
isFlushingSyncQueue = true;
let i = 0;
const previousUpdatePriority = getCurrentUpdatePriority();
try {
const isSync = true;
const queue = syncQueue;
setCurrentUpdatePriority(DiscreteEventPriority);
for (; i < queue.length; i++) {
let callback = queue[i];
do {
callback = callback(isSync);
} while (callback !== null);
}
syncQueue = null;
} finally {
setCurrentUpdatePriority(previousUpdatePriority);
isFlushingSyncQueue = false;
}
}
return null;
}
src\react-reconciler\src\ReactFiberConcurrentUpdates.js
import { HostRoot } from "./ReactWorkTags";
const concurrentQueues = [];
let concurrentQueuesIndex = 0;
export function markUpdateLaneFromFiberToRoot(sourceFiber) {
let node = sourceFiber;
let parent = sourceFiber.return;
while (parent !== null) {
node = parent;
parent = parent.return;
}
if (node.tag === HostRoot) {
const root = node.stateNode;
return root;
}
return null;
}
+export function enqueueConcurrentHookUpdate(fiber, queue, update, lane) {
+ enqueueUpdate(fiber, queue, update, lane);
return getRootForUpdatedFiber(fiber);
}
export function enqueueConcurrentClassUpdate(fiber, queue, update, lane) {
enqueueUpdate(fiber, queue, update, lane);
return getRootForUpdatedFiber(fiber);
}
function enqueueUpdate(fiber, queue, update, lane) {
concurrentQueues[concurrentQueuesIndex++] = fiber;
concurrentQueues[concurrentQueuesIndex++] = queue;
concurrentQueues[concurrentQueuesIndex++] = update;
concurrentQueues[concurrentQueuesIndex++] = lane;
}
function getRootForUpdatedFiber(sourceFiber) {
let node = sourceFiber;
let parent = node.return;
while (parent !== null) {
node = parent;
parent = node.return;
}
return node.tag === HostRoot ? node.stateNode : null;
}
export function finishQueueingConcurrentUpdates() {
const endIndex = concurrentQueuesIndex;
concurrentQueuesIndex = 0;
let i = 0;
while (i < endIndex) {
const fiber = concurrentQueues[i++];
const queue = concurrentQueues[i++];
const update = concurrentQueues[i++];
const lane = concurrentQueues[i++]
if (queue !== null && update !== null) {
const pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
}
}
src\react-dom-bindings\src\events\ReactDOMEventListener.js
import getEventTarget from "./getEventTarget";
import { getClosestInstanceFromNode } from "../client/ReactDOMComponentTree";
import { dispatchEventForPluginEventSystem } from "./DOMPluginEventSystem";
import {
DiscreteEventPriority, ContinuousEventPriority, DefaultEventPriority,
+ getCurrentUpdatePriority, setCurrentUpdatePriority
} from 'react-reconciler/src/ReactEventPriorities';
export function createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags
) {
const listenerWrapper = dispatchDiscreteEvent;
return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer);
}
+function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent) {
+ const previousPriority = getCurrentUpdatePriority();
+ try {
+ setCurrentUpdatePriority(DiscreteEventPriority);
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent)
+ } finally {
+ setCurrentUpdatePriority(previousPriority);
+ }
+}
export function dispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) {
const nativeEventTarget = getEventTarget(nativeEvent);
const targetInst = getClosestInstanceFromNode(nativeEventTarget);
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer
);
}
export function getEventPriority(domEventName) {
switch (domEventName) {
case 'click':
return DiscreteEventPriority;
case 'drag':
return ContinuousEventPriority;
default:
return DefaultEventPriority;
}
}
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
+function FunctionComponent() {
+ console.log('FunctionComponent');
+ const [number, setNumber] = React.useState(0);
+ React.useEffect(() => {
+ setNumber(number => number + 1)
+ }, []);
+ return (<button onClick={() => setNumber(number + 1)}>{number}</button>)
+}
const element = <FunctionComponent />;
const container = document.getElementById("root");
const root = createRoot(container, { unstable_concurrentUpdatesByDefault: true });
root.render(element);
src\react-reconciler\src\ReactFiberWorkLoop.js
import {
scheduleCallback as Scheduler_scheduleCallback,
ImmediatePriority as ImmediateSchedulerPriority,
UserBlockingPriority as UserBlockingSchedulerPriority,
NormalPriority as NormalSchedulerPriority,
IdlePriority as IdleSchedulerPriority,
+ shouldYield
} from "./Scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
import { completeWork } from "./ReactFiberCompleteWork";
import { MutationMask, NoFlags, Passive } from "./ReactFiberFlags";
import {
commitMutationEffects,
commitPassiveUnmountEffects,
commitPassiveMountEffects,
commitLayoutEffects,
} from "./ReactFiberCommitWork";
import { finishQueueingConcurrentUpdates } from "./ReactFiberConcurrentUpdates";
import {
NoLane, markRootUpdated, NoLanes,
getNextLanes, getHighestPriorityLane, SyncLane,
includesBlockingLane
} from './ReactFiberLane';
import {
getCurrentUpdatePriority, lanesToEventPriority, DiscreteEventPriority, ContinuousEventPriority,
DefaultEventPriority, IdleEventPriority, setCurrentUpdatePriority
} from './ReactEventPriorities';
import { getCurrentEventPriority } from 'react-dom-bindings/src/client/ReactDOMHostConfig';
import { scheduleSyncCallback, flushSyncCallbacks } from './ReactFiberSyncTaskQueue';
let workInProgress = null;
let rootDoesHavePassiveEffects = false;
let rootWithPendingPassiveEffects = null;
let workInProgressRootRenderLanes = NoLanes;
+const RootInProgress = 0;
+const RootCompleted = 5;
+let workInProgressRoot = null;
+let workInProgressRootExitStatus = RootInProgress;
export function scheduleUpdateOnFiber(root, fiber, lane) {
markRootUpdated(root, lane);
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
const nextLanes = getNextLanes(root, NoLanes);
+ if (nextLanes === NoLanes) {
+ root.callbackNode = null;
+ root.callbackPriority = NoLane;
+ return;
+ }
const newCallbackPriority = getHighestPriorityLane(nextLanes);
+ let newCallbackNode;
if (newCallbackPriority === SyncLane) {
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
queueMicrotask(flushSyncCallbacks);
+ newCallbackNode = null;
} else {
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
+ newCallbackNode = Scheduler_scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root))
}
+ root.callbackNode = newCallbackNode;
}
function performSyncWorkOnRoot(root) {
const lanes = getNextLanes(root, NoLanes);
renderRootSync(root, lanes);
const finishedWork = root.current.alternate
root.finishedWork = finishedWork
commitRoot(root)
return null;
}
function performConcurrentWorkOnRoot(root, didTimeout) {
+ const originalCallbackNode = root.callbackNode;
const lanes = getNextLanes(root, NoLanes);
if (lanes === NoLanes) {
return null;
}
const shouldTimeSlice = !includesBlockingLane(root, lanes) && (!didTimeout);
+ const exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes);
+ if (exitStatus !== RootInProgress) {
+ const finishedWork = root.current.alternate
+ root.finishedWork = finishedWork
+ commitRoot(root)
+ }
+ if (root.callbackNode === originalCallbackNode) {
+ return performConcurrentWorkOnRoot.bind(null, root);
+ }
+ return null;
}
function renderRootConcurrent(root, lanes) {
+ if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
+ prepareFreshStack(root, lanes);
+ }
+ workLoopConcurrent();
+ if (workInProgress !== null) {
+ return RootInProgress;
+ }
+ workInProgressRoot = null;
+ workInProgressRootRenderLanes = NoLanes;
+ return workInProgressRootExitStatus;
}
+function workLoopConcurrent() {
+ sleep(6);
+ performUnitOfWork(workInProgress);
+ console.log('shouldYield()', shouldYield(), workInProgress?.type);
+}
export function flushPassiveEffects() {
if (rootWithPendingPassiveEffects !== null) {
const root = rootWithPendingPassiveEffects;
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);
}
}
function commitRoot(root) {
const previousPriority = getCurrentUpdatePriority();
try {
setCurrentUpdatePriority(DiscreteEventPriority);
commitRootImpl(root);
} finally {
setCurrentUpdatePriority(previousPriority);
}
}
function commitRootImpl(root) {
const { finishedWork } = root;
+ root.callbackNode = null;
+ root.callbackPriority = NoLane;
+ workInProgressRoot = null;
+ workInProgressRootRenderLanes = NoLanes;
if ((finishedWork.subtreeFlags & Passive) !== NoFlags || (finishedWork.flags & Passive) !== NoFlags) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
Scheduler_scheduleCallback(NormalSchedulerPriority, flushPassiveEffects);
}
}
const subtreeHasEffects = (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
commitMutationEffects(finishedWork, root);
commitLayoutEffects(finishedWork, root);
root.current = finishedWork;
if (rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
}
}
root.current = finishedWork;
}
function prepareFreshStack(root, lanes) {
+ workInProgressRoot = root;
workInProgress = createWorkInProgress(root.current, null);
workInProgressRootRenderLanes = lanes;
finishQueueingConcurrentUpdates();
}
function renderRootSync(root, lanes) {
+ //不是一个根,或者是更高优先级的更新
+ if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
prepareFreshStack(root, lanes)
+ }
workLoopSync();
+ return workInProgressRootExitStatus;
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork, workInProgressRootRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
completeWork(current, completedWork);
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
+ if (workInProgressRootExitStatus === RootInProgress) {
+ workInProgressRootExitStatus = RootCompleted;
+ }
}
export function requestUpdateLane() {
const updateLane = getCurrentUpdatePriority();
if (updateLane !== NoLane) {
return updateLane;
}
const eventLane = getCurrentEventPriority();
return eventLane;
}
+function sleep(time) {
+ const timeStamp = new Date().getTime();
+ const endTime = timeStamp + time;
+ while (true) {
+ if (new Date().getTime() > endTime) {
+ return;
+ }
+ }
+}
src\react-reconciler\src\Scheduler.js
import * as Scheduler from 'scheduler'
export const scheduleCallback = Scheduler.unstable_scheduleCallback
export const NormalPriority = Scheduler.unstable_NormalPriority
export const ImmediatePriority = Scheduler.unstable_ImmediatePriority;
export const UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
export const LowPriority = Scheduler.unstable_LowPriority;
export const IdlePriority = Scheduler.unstable_IdlePriority;
+export const shouldYield = Scheduler.unstable_shouldYield
src\scheduler\src\forks\Scheduler.js
import {
ImmediatePriority,
UserBlockingPriority,
NormalPriority,
LowPriority,
IdlePriority,
} from "../SchedulerPriorities";
import { push, pop, peek } from "../SchedulerMinHeap";
import { frameYieldMs } from "../SchedulerFeatureFlags";
const maxSigned31BitInt = 1073741823;
const IMMEDIATE_PRIORITY_TIMEOUT = -1;
const USER_BLOCKING_PRIORITY_TIMEOUT = 250;
const NORMAL_PRIORITY_TIMEOUT = 5000;
const LOW_PRIORITY_TIMEOUT = 10000;
const IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;
const taskQueue = [];
let taskIdCounter = 1;
let scheduledHostCallback = null;
let startTime = -1;
let currentTask = null;
const frameInterval = frameYieldMs;
const channel = new MessageChannel();
const port = channel.port2;
const getCurrentTime = () => performance.now();
channel.port1.onmessage = performWorkUntilDeadline;
function schedulePerformWorkUntilDeadline() {
port.postMessage(null);
}
function performWorkUntilDeadline() {
if (scheduledHostCallback !== null) {
startTime = getCurrentTime();
let hasMoreWork = true;
try {
hasMoreWork = scheduledHostCallback(startTime);
} finally {
if (hasMoreWork) {
schedulePerformWorkUntilDeadline();
} else {
scheduledHostCallback = null;
}
}
}
}
function requestHostCallback(callback) {
scheduledHostCallback = callback;
schedulePerformWorkUntilDeadline();
}
function unstable_scheduleCallback(priorityLevel, callback) {
const currentTime = getCurrentTime();
const startTime = currentTime;
let timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT;
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT;
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
}
const expirationTime = startTime + timeout;
const newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
requestHostCallback(flushWork);
return newTask;
}
function flushWork(initialTime) {
return workLoop(initialTime);
}
function shouldYieldToHost() {
const timeElapsed = getCurrentTime() - startTime;
if (timeElapsed < frameInterval) {
return false;
}
return true;
}
function workLoop(initialTime) {
let currentTime = initialTime;
currentTask = peek(taskQueue);
while (currentTask !== null) {
if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
break;
}
const callback = currentTask.callback;
if (typeof callback === "function") {
currentTask.callback = null;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === "function") {
currentTask.callback = continuationCallback;
return true;
}
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
} else {
pop(taskQueue);
}
currentTask = peek(taskQueue);
}
if (currentTask !== null) {
return true;
}
return false;
}
export {
NormalPriority as unstable_NormalPriority,
unstable_scheduleCallback,
+ shouldYieldToHost as unstable_shouldYield
};
src\react-reconciler\src\ReactFiberLane.js
+import { allowConcurrentByDefault } from 'shared/ReactFeatureFlags';
export const TotalLanes = 31;
export const NoLanes = 0b0000000000000000000000000000000;
export const NoLane = 0b0000000000000000000000000000000;
export const SyncLane = 0b0000000000000000000000000000001;// 1
export const InputContinuousLane = 0b0000000000000000000000000000100;// 4
export const DefaultLane = 0b0000000000000000000000000010000;// 16
export const NonIdleLanes = 0b0001111111111111111111111111111;
export const IdleLane = 0b0100000000000000000000000000000;
export function mergeLanes(a, b) {
return a | b;
}
export function markRootUpdated(root, updateLane) {
root.pendingLanes |= updateLane;
}
export function getNextLanes(root) {
const pendingLanes = root.pendingLanes;
if (pendingLanes === NoLanes) {
return NoLanes;
}
const nextLanes = getHighestPriorityLanes(pendingLanes);
return nextLanes;
}
function getHighestPriorityLanes(lanes) {
return getHighestPriorityLane(lanes);
}
export function getHighestPriorityLane(lanes) {
return lanes & -lanes;
}
export function includesNonIdleWork(lanes) {
return (lanes & NonIdleLanes) !== NoLanes;
}
export function includesBlockingLane(root, lanes) {
+ if (allowConcurrentByDefault) {
+ return false;
+ }
const SyncDefaultLanes = InputContinuousLane | DefaultLane;
return (lanes & SyncDefaultLanes) !== NoLanes;
}
export function isSubsetOfLanes(set, subset) {
return (set & subset) === subset;
}
src\shared\ReactFeatureFlags.js
export const allowConcurrentByDefault = true;
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
+function FunctionComponent() {
+ console.log('FunctionComponent');
+ const [number, setNumber] = React.useState(0);
+ React.useEffect(() => {
+ setNumber(number => number + 1)
+ setNumber(number => number + 1)
+ }, []);
+ return (<button onClick={() => {
+ setNumber(number => number + 1)
+ setNumber(number => number + 1)
+ }}>{number}</button>)
+}
const element = <FunctionComponent />;
const container = document.getElementById("root");
const root = createRoot(container, { unstable_concurrentUpdatesByDefault: true });
root.render(element);
src\react-reconciler\src\ReactFiberHooks.js
import ReactSharedInternals from "shared/ReactSharedInternals";
import { enqueueConcurrentHookUpdate } from "./ReactFiberConcurrentUpdates";
import { scheduleUpdateOnFiber, requestUpdateLane } from "./ReactFiberWorkLoop";
import is from "shared/objectIs";
import { Passive as PassiveEffect, Update as UpdateEffect } from "./ReactFiberFlags";
import { HasEffect as HookHasEffect, Passive as HookPassive, Layout as HookLayout } from "./ReactHookEffectTags";
+import { NoLanes } from './ReactFiberLane';
const { ReactCurrentDispatcher } = ReactSharedInternals;
let currentlyRenderingFiber = null;
let workInProgressHook = null;
let currentHook = null;
const HooksDispatcherOnMountInDEV = {
useReducer: mountReducer,
useState: mountState,
useEffect: mountEffect,
useLayoutEffect: mountLayoutEffect,
};
const HooksDispatcherOnUpdateInDEV = {
useReducer: updateReducer,
useState: updateState,
useEffect: updateEffect,
useLayoutEffect: updateLayoutEffect,
};
export function useLayoutEffect(reducer, initialArg) {
return ReactCurrentDispatcher.current.useLayoutEffect(reducer, initialArg);
}
function updateLayoutEffect(create, deps) {
return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}
function mountLayoutEffect(create, deps) {
const fiberFlags = UpdateEffect;
return mountEffectImpl(fiberFlags, HookLayout, create, deps);
}
function updateEffect(create, deps) {
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, destroy, nextDeps);
}
function areHookInputsEqual(nextDeps, prevDeps) {
if (prevDeps === null) {
return false;
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
function mountEffect(create, deps) {
return mountEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, undefined, nextDeps);
}
function pushEffect(tag, create, destroy, deps) {
const effect = {
tag,
create,
destroy,
deps,
next: null,
};
let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
function createFunctionComponentUpdateQueue() {
return {
lastEffect: null,
};
}
function basicStateReducer(state, action) {
return typeof action === "function" ? action(state) : action;
}
function mountReducer(reducer, initialArg) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialArg;
const queue = {
pending: null,
dispatch: null,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function updateReducer(reducer) {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const current = currentHook;
const pendingQueue = queue.pending;
let baseQueue = null;
let newState = current.memoizedState;
if (pendingQueue !== null) {
baseQueue = pendingQueue;
queue.pending = null;
}
if (baseQueue !== null) {
const first = baseQueue.next;
let update = first;
do {
if (update.hasEagerState) {
newState = update.eagerState;
} else {
const action = update.action;
newState = reducer(newState, action);
}
update = update.next;
} while (update !== null && update !== first);
}
hook.memoizedState = newState;
queue.lastRenderedState = newState;
const dispatch = queue.dispatch;
return [hook.memoizedState, dispatch];
}
function mountState(initialState) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialState;
const queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function dispatchSetState(fiber, queue, action) {
const lane = requestUpdateLane(fiber);
const update = {
lane,
action,
hasEagerState: false,
eagerState: null,
next: null,
};
+ const alternate = fiber.alternate;
+ if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
const lastRenderedReducer = queue.lastRenderedReducer;
const currentState = queue.lastRenderedState;
const eagerState = lastRenderedReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
return;
}
+ }
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
scheduleUpdateOnFiber(root, fiber, lane);
}
function updateState(initialState) {
return updateReducer(basicStateReducer, initialState);
}
function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function dispatchReducerAction(fiber, queue, action) {
const update = {
action,
next: null,
};
const root = enqueueConcurrentHookUpdate(fiber, queue, update);
scheduleUpdateOnFiber(root, fiber);
}
function updateWorkInProgressHook() {
let nextCurrentHook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
currentHook = nextCurrentHook;
const newHook = {
memoizedState: currentHook.memoizedState,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
export function renderWithHooks(current, workInProgress, Component, props) {
currentlyRenderingFiber = workInProgress;
workInProgress.updateQueue = null;
workInProgress.memoizedState = null;
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
const children = Component(props);
currentlyRenderingFiber = null;
workInProgressHook = null;
currentHook = null;
return children;
}
src\react-reconciler\src\ReactFiberWorkLoop.js
import {
scheduleCallback as Scheduler_scheduleCallback,
ImmediatePriority as ImmediateSchedulerPriority,
UserBlockingPriority as UserBlockingSchedulerPriority,
NormalPriority as NormalSchedulerPriority,
IdlePriority as IdleSchedulerPriority,
shouldYield
} from "./Scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
import { completeWork } from "./ReactFiberCompleteWork";
import { MutationMask, NoFlags, Passive } from "./ReactFiberFlags";
import {
commitMutationEffects,
commitPassiveUnmountEffects,
commitPassiveMountEffects,
commitLayoutEffects,
} from "./ReactFiberCommitWork";
import { finishQueueingConcurrentUpdates } from "./ReactFiberConcurrentUpdates";
import {
NoLane, markRootUpdated, NoLanes,
getNextLanes, getHighestPriorityLane, SyncLane,
includesBlockingLane
} from './ReactFiberLane';
import {
getCurrentUpdatePriority, lanesToEventPriority, DiscreteEventPriority, ContinuousEventPriority,
DefaultEventPriority, IdleEventPriority, setCurrentUpdatePriority
} from './ReactEventPriorities';
import { getCurrentEventPriority } from 'react-dom-bindings/src/client/ReactDOMHostConfig';
import { scheduleSyncCallback, flushSyncCallbacks } from './ReactFiberSyncTaskQueue';
let workInProgress = null;
let rootDoesHavePassiveEffects = false;
let rootWithPendingPassiveEffects = null;
let workInProgressRootRenderLanes = NoLanes;
const RootInProgress = 0;
const RootCompleted = 5;
let workInProgressRoot = null;
let workInProgressRootExitStatus = RootInProgress;
export function scheduleUpdateOnFiber(root, fiber, lane) {
markRootUpdated(root, lane);
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
const nextLanes = getNextLanes(root, NoLanes);
if (nextLanes === NoLanes) {
root.callbackNode = null;
root.callbackPriority = NoLane;
return;
}
const newCallbackPriority = getHighestPriorityLane(nextLanes);
+ const existingCallbackPriority = root.callbackPriority;
+ if (existingCallbackPriority === newCallbackPriority) {
+ return;
+ }
+ let newCallbackNode;
if (newCallbackPriority === SyncLane) {
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
queueMicrotask(flushSyncCallbacks);
newCallbackNode = null;
} else {
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
+ newCallbackNode = Scheduler_scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root))
}
+ root.callbackPriority = newCallbackPriority;
+ root.callbackNode = newCallbackNode;
}
function performSyncWorkOnRoot(root) {
const lanes = getNextLanes(root, NoLanes);
renderRootSync(root, lanes);
const finishedWork = root.current.alternate
root.finishedWork = finishedWork
commitRoot(root)
return null;
}
function performConcurrentWorkOnRoot(root, didTimeout) {
const originalCallbackNode = root.callbackNode;
const lanes = getNextLanes(root, NoLanes);
if (lanes === NoLanes) {
return null;
}
const shouldTimeSlice = !includesBlockingLane(root, lanes) && (!didTimeout);
const exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes);
if (exitStatus !== RootInProgress) {
const finishedWork = root.current.alternate
root.finishedWork = finishedWork
commitRoot(root)
}
if (root.callbackNode === originalCallbackNode) {
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}
function renderRootConcurrent(root, lanes) {
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
prepareFreshStack(root, lanes);
}
workLoopConcurrent();
if (workInProgress !== null) {
return RootInProgress;
}
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
export function flushPassiveEffects() {
if (rootWithPendingPassiveEffects !== null) {
const root = rootWithPendingPassiveEffects;
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);
}
}
function commitRoot(root) {
const previousPriority = getCurrentUpdatePriority();
try {
setCurrentUpdatePriority(DiscreteEventPriority);
commitRootImpl(root);
} finally {
setCurrentUpdatePriority(previousPriority);
}
}
function commitRootImpl(root) {
const { finishedWork } = root;
root.callbackNode = null;
+ root.callbackPriority = NoLane;
if ((finishedWork.subtreeFlags & Passive) !== NoFlags || (finishedWork.flags & Passive) !== NoFlags) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
Scheduler_scheduleCallback(NormalSchedulerPriority, flushPassiveEffects);
}
}
const subtreeHasEffects = (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
commitMutationEffects(finishedWork, root);
commitLayoutEffects(finishedWork, root);
root.current = finishedWork;
if (rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
}
}
root.current = finishedWork;
}
function prepareFreshStack(root, lanes) {
workInProgress = createWorkInProgress(root.current, null);
workInProgressRootRenderLanes = lanes;
finishQueueingConcurrentUpdates();
}
function renderRootSync(root, lanes) {
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
prepareFreshStack(root, lanes)
}
workLoopSync();
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork, workInProgressRootRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
completeWork(current, completedWork);
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
if (workInProgressRootExitStatus === RootInProgress) {
workInProgressRootExitStatus = RootCompleted;
}
}
export function requestUpdateLane() {
const updateLane = getCurrentUpdatePriority();
if (updateLane !== NoLane) {
return updateLane;
}
const eventLane = getCurrentEventPriority();
return eventLane;
}
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
function FunctionComponent() {
+function FunctionComponent() {
+ const [numbers, setNumbers] = React.useState(new Array(10).fill('A'));
+ const divRef = React.useRef();
+ React.useEffect(() => {
+ setTimeout(() => {
+ divRef.current.click();
+ }, 10);
+ setNumbers(numbers => numbers.map(item => item + 'B'))
+ }, []);
+ return (<div ref={divRef} onClick={() => {
+ setNumbers(numbers => numbers.map(item => item + 'C'))
+ }}>{numbers.map((number, index) => <span key={index}>{number}</span>)}</div>)
+}
const element = <FunctionComponent />;
const container = document.getElementById("root");
const root = createRoot(container, { unstable_concurrentUpdatesByDefault: true });
root.render(element);
src\react\index.js
export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
useReducer,
useState,
useEffect,
useLayoutEffect,
+ useRef
} from "./src/React";
src\react\src\React.js
+import { useReducer, useState, useEffect, useLayoutEffect, useRef } from "./ReactHooks";
import ReactSharedInternals from "./ReactSharedInternals";
export {
useReducer,
useState,
useEffect,
useLayoutEffect,
+ useRef,
ReactSharedInternals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
};
src\react\src\ReactHooks.js
import ReactCurrentDispatcher from "./ReactCurrentDispatcher";
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return dispatcher;
}
export function useReducer(reducer, initialArg, init) {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg, init);
}
export function useState(initialState) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
export function useEffect(create, deps) {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, deps);
}
export function useLayoutEffect(create, deps) {
const dispatcher = resolveDispatcher();
return dispatcher.useLayoutEffect(create, deps);
}
+export function useRef(initialValue) {
+ const dispatcher = resolveDispatcher();
+ return dispatcher.useRef(initialValue);
+}
src\react-reconciler\src\ReactFiberWorkLoop.js
import {
scheduleCallback as Scheduler_scheduleCallback,
ImmediatePriority as ImmediateSchedulerPriority,
UserBlockingPriority as UserBlockingSchedulerPriority,
NormalPriority as NormalSchedulerPriority,
IdlePriority as IdleSchedulerPriority,
shouldYield,
+ cancelCallback as Scheduler_cancelCallback
} from "./Scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
import { completeWork } from "./ReactFiberCompleteWork";
import { MutationMask, NoFlags, Passive } from "./ReactFiberFlags";
import {
commitMutationEffects,
commitPassiveUnmountEffects,
commitPassiveMountEffects,
commitLayoutEffects,
} from "./ReactFiberCommitWork";
import { finishQueueingConcurrentUpdates } from "./ReactFiberConcurrentUpdates";
import {
NoLane, markRootUpdated, NoLanes,
getNextLanes, getHighestPriorityLane, SyncLane,
includesBlockingLane
} from './ReactFiberLane';
import {
getCurrentUpdatePriority, lanesToEventPriority, DiscreteEventPriority, ContinuousEventPriority,
DefaultEventPriority, IdleEventPriority, setCurrentUpdatePriority
} from './ReactEventPriorities';
import { getCurrentEventPriority } from 'react-dom-bindings/src/client/ReactDOMHostConfig';
import { scheduleSyncCallback, flushSyncCallbacks } from './ReactFiberSyncTaskQueue';
let workInProgress = null;
let rootDoesHavePassiveEffects = false;
let rootWithPendingPassiveEffects = null;
let workInProgressRootRenderLanes = NoLanes;
const RootInProgress = 0;
const RootCompleted = 5;
let workInProgressRoot = null;
let workInProgressRootExitStatus = RootInProgress;
export function scheduleUpdateOnFiber(root, fiber, lane) {
markRootUpdated(root, lane);
ensureRootIsScheduled(root);
}
function ensureRootIsScheduled(root) {
+ const existingCallbackNode = root.callbackNode;
+ const nextLanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);
if (nextLanes === NoLanes) {
root.callbackNode = null;
root.callbackPriority = NoLane;
return;
}
const newCallbackPriority = getHighestPriorityLane(nextLanes);
const existingCallbackPriority = root.callbackPriority;
if (existingCallbackPriority === newCallbackPriority) {
return;
}
+ if (existingCallbackNode != null) {
+ Scheduler_cancelCallback(existingCallbackNode);
+ }
let newCallbackNode;
if (newCallbackPriority === SyncLane) {
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
queueMicrotask(flushSyncCallbacks);
newCallbackNode = null;
} else {
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
newCallbackNode = Scheduler_scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root))
}
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
function performSyncWorkOnRoot(root) {
const lanes = getNextLanes(root, NoLanes);
renderRootSync(root, lanes);
const finishedWork = root.current.alternate
root.finishedWork = finishedWork
commitRoot(root)
return null;
}
function performConcurrentWorkOnRoot(root, didTimeout) {
const originalCallbackNode = root.callbackNode;
const lanes = getNextLanes(root, NoLanes);
if (lanes === NoLanes) {
return null;
}
const shouldTimeSlice = !includesBlockingLane(root, lanes) && (!didTimeout);
const exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes);
if (exitStatus !== RootInProgress) {
const finishedWork = root.current.alternate
root.finishedWork = finishedWork
commitRoot(root)
}
if (root.callbackNode === originalCallbackNode) {
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}
function renderRootConcurrent(root, lanes) {
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
prepareFreshStack(root, lanes);
}
workLoopConcurrent();
if (workInProgress !== null) {
return RootInProgress;
}
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
export function flushPassiveEffects() {
if (rootWithPendingPassiveEffects !== null) {
const root = rootWithPendingPassiveEffects;
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);
}
}
function commitRoot(root) {
const previousPriority = getCurrentUpdatePriority();
try {
setCurrentUpdatePriority(DiscreteEventPriority);
commitRootImpl(root);
} finally {
setCurrentUpdatePriority(previousPriority);
}
}
function commitRootImpl(root) {
const { finishedWork } = root;
console.log('commit', finishedWork.child.memoizedState.memoizedState);
root.callbackNode = null;
root.callbackPriority = NoLane;
if ((finishedWork.subtreeFlags & Passive) !== NoFlags || (finishedWork.flags & Passive) !== NoFlags) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
Scheduler_scheduleCallback(NormalSchedulerPriority, flushPassiveEffects);
}
}
const subtreeHasEffects = (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
commitMutationEffects(finishedWork, root);
commitLayoutEffects(finishedWork, root);
root.current = finishedWork;
if (rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
}
}
root.current = finishedWork;
}
function prepareFreshStack(root, lanes) {
workInProgress = createWorkInProgress(root.current, null);
workInProgressRootRenderLanes = lanes;
finishQueueingConcurrentUpdates();
}
function renderRootSync(root, lanes) {
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
prepareFreshStack(root, lanes)
}
workLoopSync();
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork, workInProgressRootRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
completeWork(current, completedWork);
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
if (workInProgressRootExitStatus === RootInProgress) {
workInProgressRootExitStatus = RootCompleted;
}
}
export function requestUpdateLane() {
const updateLane = getCurrentUpdatePriority();
if (updateLane !== NoLane) {
return updateLane;
}
const eventLane = getCurrentEventPriority();
return eventLane;
}
src\react-reconciler\src\ReactFiberLane.js
import { allowConcurrentByDefault } from 'shared/ReactFeatureFlags';
export const TotalLanes = 31;
export const NoLanes = 0b0000000000000000000000000000000;
export const NoLane = 0b0000000000000000000000000000000;
export const SyncLane = 0b0000000000000000000000000000001;// 1
export const InputContinuousLane = 0b0000000000000000000000000000100;// 4
export const DefaultLane = 0b0000000000000000000000000010000;// 16
export const NonIdleLanes = 0b0001111111111111111111111111111;
export const IdleLane = 0b0100000000000000000000000000000;
export function mergeLanes(a, b) {
return a | b;
}
export function markRootUpdated(root, updateLane) {
root.pendingLanes |= updateLane;
}
+export function getNextLanes(root, wipLanes) {
const pendingLanes = root.pendingLanes;
if (pendingLanes === NoLanes) {
return NoLanes;
}
const nextLanes = getHighestPriorityLanes(pendingLanes);
+ if (wipLanes !== NoLanes && wipLanes !== nextLanes) {
+ if (nextLanes >= wipLanes) {
+ return wipLanes;
+ }
+ }
return nextLanes;
}
function getHighestPriorityLanes(lanes) {
return getHighestPriorityLane(lanes);
}
export function getHighestPriorityLane(lanes) {
return lanes & -lanes;
}
export function includesNonIdleWork(lanes) {
return (lanes & NonIdleLanes) !== NoLanes;
}
export function includesBlockingLane(root, lanes) {
if (allowConcurrentByDefault) {
return false;
}
const SyncDefaultLanes = InputContinuousLane | DefaultLane;
return (lanes & SyncDefaultLanes) !== NoLanes;
}
export function isSubsetOfLanes(set, subset) {
return (set & subset) === subset;
}
src\react-reconciler\src\ReactFiberHooks.js
import ReactSharedInternals from "shared/ReactSharedInternals";
import { enqueueConcurrentHookUpdate } from "./ReactFiberConcurrentUpdates";
import { scheduleUpdateOnFiber, requestUpdateLane } from "./ReactFiberWorkLoop";
import is from "shared/objectIs";
import { Passive as PassiveEffect, Update as UpdateEffect } from "./ReactFiberFlags";
import { HasEffect as HookHasEffect, Passive as HookPassive, Layout as HookLayout } from "./ReactHookEffectTags";
import { NoLanes } from './ReactFiberLane';
const { ReactCurrentDispatcher } = ReactSharedInternals;
let currentlyRenderingFiber = null;
let workInProgressHook = null;
let currentHook = null;
const HooksDispatcherOnMountInDEV = {
useReducer: mountReducer,
useState: mountState,
useEffect: mountEffect,
useLayoutEffect: mountLayoutEffect,
+ useRef: mountRef,
};
const HooksDispatcherOnUpdateInDEV = {
useReducer: updateReducer,
useState: updateState,
useEffect: updateEffect,
useLayoutEffect: updateLayoutEffect,
+ useRef: updateRef
};
+function mountRef(initialValue) {
+ const hook = mountWorkInProgressHook();
+ const ref = {
+ current: initialValue,
+ };
+ hook.memoizedState = ref;
+ return ref;
+}
+function updateRef() {
+ const hook = updateWorkInProgressHook();
+ return hook.memoizedState;
+}
export function useLayoutEffect(reducer, initialArg) {
return ReactCurrentDispatcher.current.useLayoutEffect(reducer, initialArg);
}
function updateLayoutEffect(create, deps) {
return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}
function mountLayoutEffect(create, deps) {
const fiberFlags = UpdateEffect;
return mountEffectImpl(fiberFlags, HookLayout, create, deps);
}
function updateEffect(create, deps) {
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, destroy, nextDeps);
}
function areHookInputsEqual(nextDeps, prevDeps) {
if (prevDeps === null) {
return false;
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
function mountEffect(create, deps) {
return mountEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, undefined, nextDeps);
}
function pushEffect(tag, create, destroy, deps) {
const effect = {
tag,
create,
destroy,
deps,
next: null,
};
let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
function createFunctionComponentUpdateQueue() {
return {
lastEffect: null,
};
}
function basicStateReducer(state, action) {
return typeof action === "function" ? action(state) : action;
}
function mountReducer(reducer, initialArg) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialArg;
const queue = {
pending: null,
dispatch: null,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function updateReducer(reducer) {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const current = currentHook;
const pendingQueue = queue.pending;
let baseQueue = null;
let newState = current.memoizedState;
if (pendingQueue !== null) {
baseQueue = pendingQueue;
queue.pending = null;
}
if (baseQueue !== null) {
const first = baseQueue.next;
let update = first;
do {
if (update.hasEagerState) {
newState = update.eagerState;
} else {
const action = update.action;
newState = reducer(newState, action);
}
update = update.next;
} while (update !== null && update !== first);
}
hook.memoizedState = newState;
queue.lastRenderedState = newState;
const dispatch = queue.dispatch;
return [hook.memoizedState, dispatch];
}
function mountState(initialState) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialState;
const queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function dispatchSetState(fiber, queue, action) {
const lane = requestUpdateLane(fiber);
const update = {
lane,
action,
hasEagerState: false,
eagerState: null,
next: null,
};
const alternate = fiber.alternate;
if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
const lastRenderedReducer = queue.lastRenderedReducer;
const currentState = queue.lastRenderedState;
const eagerState = lastRenderedReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
return;
}
}
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
scheduleUpdateOnFiber(root, fiber, lane);
}
function updateState(initialState) {
return updateReducer(basicStateReducer, initialState);
}
function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function dispatchReducerAction(fiber, queue, action) {
const update = {
action,
next: null,
};
const root = enqueueConcurrentHookUpdate(fiber, queue, update);
scheduleUpdateOnFiber(root, fiber);
}
function updateWorkInProgressHook() {
let nextCurrentHook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
currentHook = nextCurrentHook;
const newHook = {
memoizedState: currentHook.memoizedState,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
export function renderWithHooks(current, workInProgress, Component, props) {
currentlyRenderingFiber = workInProgress;
workInProgress.updateQueue = null;
workInProgress.memoizedState = null;
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
const children = Component(props);
currentlyRenderingFiber = null;
workInProgressHook = null;
currentHook = null;
return children;
}
src\react-reconciler\src\ReactFiberFlags.js
export const NoFlags = 0b00000000000000000000000000;
export const Placement = 0b00000000000000000000000010;
export const Update = 0b00000000000000000000000100;
export const ChildDeletion = 0b00000000000000000000001000;
export const Passive = 0b00000000000000010000000000;
export const LayoutMask = Update;
+export const Ref = 0b00000000000000000100000000;
+export const MutationMask = Placement | Update | Ref;
src\react-reconciler\src\ReactFiberCompleteWork.js
import {
appendInitialChild,
createInstance,
createTextInstance,
finalizeInitialChildren,
prepareUpdate,
} from "react-dom-bindings/src/client/ReactDOMHostConfig";
import { HostComponent, HostRoot, HostText, FunctionComponent } from "./ReactWorkTags";
+import { Ref, NoFlags, Update } from "./ReactFiberFlags";
+
+function markRef(workInProgress) {
+ workInProgress.flags |= Ref;
+}
function bubbleProperties(completedWork) {
let subtreeFlags = NoFlags;
let child = completedWork.child;
while (child !== null) {
subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;
child = child.sibling;
}
completedWork.subtreeFlags |= subtreeFlags;
}
function appendAllChildren(parent, workInProgress) {
// 我们只有创建的顶级fiber,但需要递归其子节点来查找所有终端节点
let node = workInProgress.child;
while (node !== null) {
// 如果是原生节点,直接添加到父节点上
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
// 再看看第一个节节点是不是原生节点
} else if (node.child !== null) {
// node.child.return = node
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
// 如果没有弟弟就找父亲的弟弟
while (node.sibling === null) {
// 如果找到了根节点或者回到了原节点结束
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
// node.sibling.return = node.return
// 下一个弟弟节点
node = node.sibling;
}
}
function markUpdate(workInProgress) {
workInProgress.flags |= Update;
}
function updateHostComponent(current, workInProgress, type, newProps) {
const oldProps = current.memoizedProps;
const instance = workInProgress.stateNode;
const updatePayload = prepareUpdate(instance, type, oldProps, newProps);
workInProgress.updateQueue = updatePayload;
if (updatePayload) {
markUpdate(workInProgress);
}
}
export function completeWork(current, workInProgress) {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent: {
const { type } = workInProgress;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, type, newProps);
+ if (current.ref !== workInProgress.ref) {
+ markRef(workInProgress);
+ }
} else {
const instance = createInstance(type, newProps, workInProgress);
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;
finalizeInitialChildren(instance, type, newProps);
+ if (workInProgress.ref !== null) {
+ markRef(workInProgress);
+ }
}
bubbleProperties(workInProgress);
return null;
break;
}
case FunctionComponent:
bubbleProperties(workInProgress);
break;
case HostRoot:
bubbleProperties(workInProgress);
break;
case HostText: {
const newText = newProps;
workInProgress.stateNode = createTextInstance(newText);
bubbleProperties(workInProgress);
break;
}
default:
break;
}
}
src\react-reconciler\src\ReactFiberCommitWork.js
import { HostRoot, HostComponent, HostText, FunctionComponent } from "./ReactWorkTags";
+import { Passive, MutationMask, Placement, Update, LayoutMask, Ref } from "./ReactFiberFlags";
import { insertBefore, appendChild, commitUpdate, removeChild } from "react-dom-bindings/src/client/ReactDOMHostConfig";
import { HasEffect as HookHasEffect, Passive as HookPassive, Layout as HookLayout } from "./ReactHookEffectTags";
export function commitMutationEffects(finishedWork, root) {
commitMutationEffectsOnFiber(finishedWork, root);
}
export function commitPassiveUnmountEffects(finishedWork) {
commitPassiveUnmountOnFiber(finishedWork);
}
function commitPassiveUnmountOnFiber(finishedWork) {
switch (finishedWork.tag) {
case FunctionComponent: {
recursivelyTraversePassiveUnmountEffects(finishedWork);
if (finishedWork.flags & Passive) {
commitHookPassiveUnmountEffects(finishedWork, finishedWork.return, HookPassive | HookHasEffect);
}
break;
}
default: {
recursivelyTraversePassiveUnmountEffects(finishedWork);
break;
}
}
}
function recursivelyTraversePassiveUnmountEffects(parentFiber) {
if (parentFiber.subtreeFlags & Passive) {
let child = parentFiber.child;
while (child !== null) {
commitPassiveUnmountOnFiber(child);
child = child.sibling;
}
}
}
function commitHookPassiveUnmountEffects(finishedWork, nearestMountedAncestor, hookFlags) {
commitHookEffectListUnmount(hookFlags, finishedWork, nearestMountedAncestor);
}
function commitHookEffectListUnmount(flags, finishedWork) {
const updateQueue = finishedWork.updateQueue;
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
destroy();
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
export function commitPassiveMountEffects(root, finishedWork) {
commitPassiveMountOnFiber(root, finishedWork);
}
function commitPassiveMountOnFiber(finishedRoot, finishedWork) {
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent: {
recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork);
if (flags & Passive) {
commitHookPassiveMountEffects(finishedWork, HookPassive | HookHasEffect);
}
break;
}
case HostRoot: {
recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork);
break;
}
default:
break;
}
}
function commitHookPassiveMountEffects(finishedWork, hookFlags) {
commitHookEffectListMount(hookFlags, finishedWork);
}
function commitHookEffectListMount(flags, finishedWork) {
const updateQueue = finishedWork.updateQueue;
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
const create = effect.create;
effect.destroy = create();
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
function recursivelyTraversePassiveMountEffects(root, parentFiber) {
if (parentFiber.subtreeFlags & Passive) {
let child = parentFiber.child;
while (child !== null) {
commitPassiveMountOnFiber(root, child);
child = child.sibling;
}
}
}
let hostParent = null;
function commitDeletionEffects(root, returnFiber, deletedFiber) {
let parent = returnFiber;
findParent: while (parent !== null) {
switch (parent.tag) {
case HostComponent: {
hostParent = parent.stateNode;
break findParent;
}
case HostRoot: {
hostParent = parent.stateNode.containerInfo;
break findParent;
}
default:
break;
}
parent = parent.return;
}
commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
hostParent = null;
}
function commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, deletedFiber) {
switch (deletedFiber.tag) {
case HostComponent:
case HostText: {
recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
if (hostParent !== null) {
removeChild(hostParent, deletedFiber.stateNode);
}
break;
}
default:
break;
}
}
function recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, parent) {
let child = parent.child;
while (child !== null) {
commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);
child = child.sibling;
}
}
function recursivelyTraverseMutationEffects(root, parentFiber) {
const deletions = parentFiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
commitDeletionEffects(root, parentFiber, childToDelete);
}
}
if (parentFiber.subtreeFlags & MutationMask) {
let { child } = parentFiber;
while (child !== null) {
commitMutationEffectsOnFiber(child, root);
child = child.sibling;
}
}
}
function isHostParent(fiber) {
return fiber.tag === HostComponent || fiber.tag === HostRoot;
}
function getHostParentFiber(fiber) {
let parent = fiber.return;
while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}
parent = parent.return;
}
return parent;
}
function insertOrAppendPlacementNode(node, before, parent) {
const { tag } = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost) {
const { stateNode } = node;
if (before) {
insertBefore(parent, stateNode, before);
} else {
appendChild(parent, stateNode);
}
} else {
const { child } = node;
if (child !== null) {
insertOrAppendPlacementNode(child, before, parent);
let { sibling } = child;
while (sibling !== null) {
insertOrAppendPlacementNode(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}
function getHostSibling(fiber) {
let node = fiber;
siblings: while (true) {
// 如果我们没有找到任何东西,让我们试试下一个弟弟
while (node.sibling === null) {
if (node.return === null || isHostParent(node.return)) {
// 如果我们是根Fiber或者父亲是原生节点,我们就是最后的弟弟
return null;
}
node = node.return;
}
// node.sibling.return = node.return
node = node.sibling;
while (node.tag !== HostComponent && node.tag !== HostText) {
// 如果它不是原生节点,并且,我们可能在其中有一个原生节点
// 试着向下搜索,直到找到为止
if (node.flags & Placement) {
// 如果我们没有孩子,可以试试弟弟
continue siblings;
} else {
// node.child.return = node
node = node.child;
}
} // Check if this host node is stable or about to be placed.
// 检查此原生节点是否稳定可以放置
if (!(node.flags & Placement)) {
// 找到它了!
return node.stateNode;
}
}
}
function commitPlacement(finishedWork) {
const parentFiber = getHostParentFiber(finishedWork);
switch (parentFiber.tag) {
case HostComponent: {
const parent = parentFiber.stateNode;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
case HostRoot: {
const parent = parentFiber.stateNode.containerInfo;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
default:
break;
}
}
function commitReconciliationEffects(finishedWork) {
const { flags } = finishedWork;
if (flags & Placement) {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}
}
export function commitMutationEffectsOnFiber(finishedWork, root) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case HostRoot: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
case FunctionComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork, finishedWork.return);
}
break;
}
case HostComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
+ if (flags & Ref) {
+ commitAttachRef(finishedWork);
+ }
if (flags & Update) {
const instance = finishedWork.stateNode;
if (instance != null) {
const newProps = finishedWork.memoizedProps;
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
const updatePayload = finishedWork.updateQueue;
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(instance, updatePayload, type, oldProps, newProps, finishedWork);
}
}
}
break;
}
case HostText: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
default: {
break;
}
}
}
+function commitAttachRef(finishedWork) {
+ const ref = finishedWork.ref;
+ if (ref !== null) {
+ const instance = finishedWork.stateNode;
+ if (typeof ref === "function") {
+ ref(instance)
+ } else {
+ ref.current = instance;
+ }
+ }
+}
export function commitLayoutEffects(finishedWork, root) {
const current = finishedWork.alternate;
commitLayoutEffectOnFiber(root, current, finishedWork);
}
function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork) {
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent: {
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
if (flags & Update) {
commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);
}
break;
}
case HostRoot: {
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
break;
}
default:
break;
}
}
function recursivelyTraverseLayoutEffects(root, parentFiber) {
if (parentFiber.subtreeFlags & LayoutMask) {
let child = parentFiber.child;
while (child !== null) {
const current = child.alternate;
commitLayoutEffectOnFiber(root, current, child);
child = child.sibling;
}
}
}
function commitHookLayoutEffects(finishedWork, hookFlags) {
commitHookEffectListMount(hookFlags, finishedWork);
}
src\react-reconciler\src\ReactFiber.js
import { HostRoot, IndeterminateComponent, HostComponent, HostText } from "./ReactWorkTags";
import { NoFlags } from "./ReactFiberFlags";
export function FiberNode(tag, pendingProps, key) {
this.tag = tag;
this.key = key;
this.type = null;
this.stateNode = null;
this.return = null;
this.child = null;
this.sibling = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
this.alternate = null;
this.index = 0;
+ this.ref = null;
}
function createFiber(tag, pendingProps, key) {
return new FiberNode(tag, pendingProps, key);
}
export function createHostRootFiber() {
return createFiber(HostRoot, null, null);
}
// We use a double buffering pooling technique because we know that we'll
// only ever need at most two versions of a tree. We pool the "other" unused
// node that we're free to reuse. This is lazily created to avoid allocating
// extra objects for things that are never updated. It also allow us to
// reclaim the extra memory if needed.
//我们使用双缓冲池技术,因为我们知道一棵树最多只需要两个版本
//我们将“其他”未使用的我们可以自由重用的节点
//这是延迟创建的,以避免分配从未更新的内容的额外对象。它还允许我们如果需要,回收额外的内存
export function createWorkInProgress(current, pendingProps) {
let workInProgress = current.alternate;
if (workInProgress === null) {
workInProgress = createFiber(current.tag, pendingProps, current.key);
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
workInProgress.type = current.type;
workInProgress.flags = NoFlags;
workInProgress.subtreeFlags = NoFlags;
}
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
+ workInProgress.ref = current.ref;
return workInProgress;
}
export function createFiberFromTypeAndProps(type, key, pendingProps) {
let fiberTag = IndeterminateComponent;
if (typeof type === "string") {
fiberTag = HostComponent;
}
const fiber = createFiber(fiberTag, pendingProps, key);
fiber.type = type;
return fiber;
}
export function createFiberFromElement(element) {
const { type } = element;
const { key } = element;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(type, key, pendingProps);
return fiber;
}
export function createFiberFromText(content) {
const fiber = createFiber(HostText, content, null);
return fiber;
}
src\react-reconciler\src\ReactChildFiber.js
import { REACT_ELEMENT_TYPE } from "shared/ReactSymbols";
import isArray from "shared/isArray";
import { createFiberFromElement, FiberNode, createFiberFromText, createWorkInProgress } from "./ReactFiber";
import { Placement, ChildDeletion } from "./ReactFiberFlags";
import { HostText } from "./ReactWorkTags";
function createChildReconciler(shouldTrackSideEffects) {
function useFiber(fiber, pendingProps) {
const clone = createWorkInProgress(fiber, pendingProps);
clone.index = 0;
clone.sibling = null;
return clone;
}
function deleteChild(returnFiber, childToDelete) {
if (!shouldTrackSideEffects) {
return;
}
const deletions = returnFiber.deletions;
if (deletions === null) {
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(childToDelete);
}
}
function deleteRemainingChildren(returnFiber, currentFirstChild) {
if (!shouldTrackSideEffects) {
return null;
}
let childToDelete = currentFirstChild;
while (childToDelete !== null) {
deleteChild(returnFiber, childToDelete);
childToDelete = childToDelete.sibling;
}
return null;
}
function reconcileSingleElement(returnFiber, currentFirstChild, element) {
const key = element.key;
let child = currentFirstChild;
while (child !== null) {
if (child.key === key) {
const elementType = element.type;
if (child.type === elementType) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
+ existing.ref = element.ref;
existing.return = returnFiber;
return existing;
}
deleteRemainingChildren(returnFiber, child);
break;
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
const created = createFiberFromElement(element);
+ created.ref = element.ref;
created.return = returnFiber;
return created;
}
function placeSingleChild(newFiber) {
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags |= Placement;
}
return newFiber;
}
function reconcileSingleTextNode(returnFiber, currentFirstChild, content) {
const created = new FiberNode(HostText, { content }, null);
created.return = returnFiber;
return created;
}
function createChild(returnFiber, newChild) {
if ((typeof newChild === "string" && newChild !== "") || typeof newChild === "number") {
const created = createFiberFromText(`${newChild}`);
created.return = returnFiber;
return created;
}
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(newChild);
+ created.ref = newChild.ref;
created.return = returnFiber;
return created;
}
default:
break;
}
}
return null;
}
function placeChild(newFiber, lastPlacedIndex, newIndex) {
newFiber.index = newIndex;
if (!shouldTrackSideEffects) {
return lastPlacedIndex;
}
const current = newFiber.alternate;
if (current !== null) {
const oldIndex = current.index;
if (oldIndex < lastPlacedIndex) {
newFiber.flags |= Placement;
return lastPlacedIndex;
} else {
return oldIndex;
}
} else {
newFiber.flags |= Placement;
return lastPlacedIndex;
}
}
function updateElement(returnFiber, current, element) {
const elementType = element.type;
if (current !== null) {
if (current.type === elementType) {
const existing = useFiber(current, element.props);
+ existing.ref = element.ref;
existing.return = returnFiber;
return existing;
}
}
const created = createFiberFromElement(element);
+ created.ref = element.ref;
created.return = returnFiber;
return created;
}
function updateSlot(returnFiber, oldFiber, newChild) {
const key = oldFiber !== null ? oldFiber.key : null;
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
if (newChild.key === key) {
return updateElement(returnFiber, oldFiber, newChild);
} else {
return null;
}
}
default:
return null;
}
}
}
function mapRemainingChildren(returnFiber, currentFirstChild) {
const existingChildren = new Map();
let existingChild = currentFirstChild;
while (existingChild !== null) {
if (existingChild.key !== null) {
existingChildren.set(existingChild.key, existingChild);
} else {
existingChildren.set(existingChild.index, existingChild);
}
existingChild = existingChild.sibling;
}
return existingChildren;
}
function updateTextNode(returnFiber, current, textContent) {
if (current === null || current.tag !== HostText) {
const created = createFiberFromText(textContent, returnFiber.mode);
created.return = returnFiber;
return created;
} else {
const existing = useFiber(current, textContent);
existing.return = returnFiber;
return existing;
}
}
function updateFromMap(existingChildren, returnFiber, newIdx, newChild) {
if ((typeof newChild === "string" && newChild !== "") || typeof newChild === "number") {
const matchedFiber = existingChildren.get(newIdx) || null;
return updateTextNode(returnFiber, matchedFiber, "" + newChild);
}
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const matchedFiber = existingChildren.get(newChild.key === null ? newIdx : newChild.key) || null;
return updateElement(returnFiber, matchedFiber, newChild);
}
}
}
return null;
}
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) {
let resultingFirstChild = null;
let previousNewFiber = null;
let newIdx = 0;
let oldFiber = currentFirstChild;
let nextOldFiber = null;
let lastPlacedIndex = 0;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
nextOldFiber = oldFiber.sibling;
const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx]);
if (newFiber === null) {
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (newIdx === newChildren.length) {
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
if (oldFiber === null) {
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx]);
if (newFiber === null) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap(existingChildren, returnFiber, newIdx, newChildren[newIdx]);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
existingChildren.delete(newFiber.key === null ? newIdx : newFiber.key);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
existingChildren.forEach((child) => deleteChild(returnFiber, child));
}
return resultingFirstChild;
}
function reconcileChildFibers(returnFiber, currentFirstChild, newChild) {
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild));
}
default:
break;
}
if (isArray(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild);
}
}
if (typeof newChild === "string") {
return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, newChild));
}
return null;
}
return reconcileChildFibers;
}
export const reconcileChildFibers = createChildReconciler(true);
export const mountChildFibers = createChildReconciler(false);
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
+let counter = 0;
+let timer;
+let bCounter = 0;
+let cCounter = 0;
+function FunctionComponent() {
+ const [numbers, setNumbers] = React.useState(new Array(100).fill('A'));
+ const divRef = React.useRef();
+ const updateB = (numbers) => new Array(100).fill(numbers[0] + 'B')
+ updateB.id = 'updateB' + (bCounter++);
+ const updateC = (numbers) => new Array(100).fill(numbers[0] + 'C')
+ updateC.id = 'updateC' + (cCounter++);
+ React.useEffect(() => {
+ timer = setInterval(() => {
+ console.log(divRef);
+ divRef.current.click();
+ if (counter++ === 0) {
+ setNumbers(updateB)
+ }
+ divRef.current.click();
+ if (counter++ > 10) {
+ clearInterval(timer);
+ }
+ });
+ }, []);
+ return (<div ref={divRef} onClick={() => setNumbers(updateC)}>
+ {numbers.map((number, index) => <span key={index}>{number}</span>)}</ div>)
+}
const element = <FunctionComponent />;
const container = document.getElementById("root");
const root = createRoot(container, { unstable_concurrentUpdatesByDefault: true });
root.render(element);
src\scheduler\src\forks\Scheduler.js
import {
ImmediatePriority,
UserBlockingPriority,
NormalPriority,
LowPriority,
IdlePriority,
} from "../SchedulerPriorities";
import { push, pop, peek } from "../SchedulerMinHeap";
import { frameYieldMs } from "../SchedulerFeatureFlags";
const maxSigned31BitInt = 1073741823;
const IMMEDIATE_PRIORITY_TIMEOUT = -1;
const USER_BLOCKING_PRIORITY_TIMEOUT = 250;
const NORMAL_PRIORITY_TIMEOUT = 5000;
const LOW_PRIORITY_TIMEOUT = 10000;
const IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;
const taskQueue = [];
let taskIdCounter = 1;
let scheduledHostCallback = null;
let startTime = -1;
let currentTask = null;
const frameInterval = frameYieldMs;
const channel = new MessageChannel();
const port = channel.port2;
const getCurrentTime = () => performance.now();
channel.port1.onmessage = performWorkUntilDeadline;
function schedulePerformWorkUntilDeadline() {
port.postMessage(null);
}
function performWorkUntilDeadline() {
if (scheduledHostCallback !== null) {
startTime = getCurrentTime();
let hasMoreWork = true;
try {
hasMoreWork = scheduledHostCallback(startTime);
} finally {
if (hasMoreWork) {
schedulePerformWorkUntilDeadline();
} else {
scheduledHostCallback = null;
}
}
}
}
function requestHostCallback(callback) {
scheduledHostCallback = callback;
schedulePerformWorkUntilDeadline();
}
function unstable_scheduleCallback(priorityLevel, callback) {
const currentTime = getCurrentTime();
const startTime = currentTime;
let timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT;
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT;
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
}
const expirationTime = startTime + timeout;
const newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
requestHostCallback(flushWork);
return newTask;
}
function flushWork(initialTime) {
return workLoop(initialTime);
}
function shouldYieldToHost() {
const timeElapsed = getCurrentTime() - startTime;
if (timeElapsed < frameInterval) {
return false;
}
return true;
}
function workLoop(initialTime) {
let currentTime = initialTime;
currentTask = peek(taskQueue);
while (currentTask !== null) {
if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
break;
}
const callback = currentTask.callback;
if (typeof callback === "function") {
currentTask.callback = null;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === "function") {
currentTask.callback = continuationCallback;
return true;
}
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
} else {
pop(taskQueue);
}
currentTask = peek(taskQueue);
}
if (currentTask !== null) {
return true;
}
return false;
}
+function unstable_cancelCallback(task) {
+ task.callback = null;
+}
export {
NormalPriority as unstable_NormalPriority,
unstable_scheduleCallback,
shouldYieldToHost as unstable_shouldYield,
+ unstable_cancelCallback,
+ getCurrentTime as unstable_now
};
src\react-reconciler\src\Scheduler.js
import * as Scheduler from 'scheduler'
export const scheduleCallback = Scheduler.unstable_scheduleCallback
export const NormalPriority = Scheduler.unstable_NormalPriority
export const ImmediatePriority = Scheduler.unstable_ImmediatePriority;
export const UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
export const LowPriority = Scheduler.unstable_LowPriority;
export const IdlePriority = Scheduler.unstable_IdlePriority;
export const shouldYield = Scheduler.unstable_shouldYield
+export const cancelCallback = Scheduler.unstable_cancelCallback
+export const now = Scheduler.unstable_now;
src\react-reconciler\src\ReactFiberWorkLoop.js
import {
scheduleCallback as Scheduler_scheduleCallback,
ImmediatePriority as ImmediateSchedulerPriority,
UserBlockingPriority as UserBlockingSchedulerPriority,
NormalPriority as NormalSchedulerPriority,
IdlePriority as IdleSchedulerPriority,
shouldYield,
+ cancelCallback as Scheduler_cancelCallback,
+ now
} from "./Scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
import { completeWork } from "./ReactFiberCompleteWork";
import { MutationMask, NoFlags, Passive } from "./ReactFiberFlags";
import {
commitMutationEffects,
commitPassiveUnmountEffects,
commitPassiveMountEffects,
commitLayoutEffects,
} from "./ReactFiberCommitWork";
import { finishQueueingConcurrentUpdates } from "./ReactFiberConcurrentUpdates";
import {
NoLane, markRootUpdated, NoLanes,
getNextLanes, getHighestPriorityLane, SyncLane,
+ includesBlockingLane, markStarvedLanesAsExpired, includesExpiredLane,
+ mergeLanes, markRootFinished, NoTimestamp
} from './ReactFiberLane';
import {
getCurrentUpdatePriority, lanesToEventPriority, DiscreteEventPriority, ContinuousEventPriority,
DefaultEventPriority, IdleEventPriority, setCurrentUpdatePriority
} from './ReactEventPriorities';
import { getCurrentEventPriority } from 'react-dom-bindings/src/client/ReactDOMHostConfig';
import { scheduleSyncCallback, flushSyncCallbacks } from './ReactFiberSyncTaskQueue';
let workInProgress = null;
let rootDoesHavePassiveEffects = false;
let rootWithPendingPassiveEffects = null;
let workInProgressRootRenderLanes = NoLanes;
const RootInProgress = 0;
const RootCompleted = 5;
let workInProgressRoot = null;
let workInProgressRootExitStatus = RootInProgress;
+let currentEventTime = NoTimestamp;
+function cancelCallback(callbackNode) {
+ console.log('cancelCallback');
+ return Scheduler_cancelCallback(callbackNode);
+}
+export function scheduleUpdateOnFiber(root, fiber, lane, eventTime) {
markRootUpdated(root, lane);
+ ensureRootIsScheduled(root, eventTime);
}
+function ensureRootIsScheduled(root, currentTime) {
const existingCallbackNode = root.callbackNode;
+ markStarvedLanesAsExpired(root, currentTime);
const nextLanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);
if (nextLanes === NoLanes) {
root.callbackNode = null;
root.callbackPriority = NoLane;
return;
}
const newCallbackPriority = getHighestPriorityLane(nextLanes);
const existingCallbackPriority = root.callbackPriority;
if (existingCallbackPriority === newCallbackPriority) {
return;
}
if (existingCallbackNode != null) {
cancelCallback(existingCallbackNode);
}
let newCallbackNode;
if (newCallbackPriority === SyncLane) {
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
queueMicrotask(flushSyncCallbacks);
newCallbackNode = null;
} else {
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
newCallbackNode = Scheduler_scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root))
}
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
function performSyncWorkOnRoot(root) {
const lanes = getNextLanes(root, NoLanes);
renderRootSync(root, lanes);
const finishedWork = root.current.alternate
root.finishedWork = finishedWork
commitRoot(root)
return null;
}
function performConcurrentWorkOnRoot(root, didTimeout) {
const originalCallbackNode = root.callbackNode;
const lanes = getNextLanes(root, NoLanes);
if (lanes === NoLanes) {
return null;
}
+ const nonIncludesBlockingLane = !includesBlockingLane(root, lanes);
+ const nonIncludesExpiredLane = !includesExpiredLane(root, lanes);
+ const nonTimeout = !didTimeout;
+ const shouldTimeSlice = nonIncludesBlockingLane && nonIncludesExpiredLane && nonTimeout;
const exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes);
if (exitStatus !== RootInProgress) {
const finishedWork = root.current.alternate
root.finishedWork = finishedWork
commitRoot(root)
}
if (root.callbackNode === originalCallbackNode) {
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}
function renderRootConcurrent(root, lanes) {
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
prepareFreshStack(root, lanes);
}
workLoopConcurrent();
if (workInProgress !== null) {
return RootInProgress;
}
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
+ sleep(5)
performUnitOfWork(workInProgress);
}
}
export function flushPassiveEffects() {
if (rootWithPendingPassiveEffects !== null) {
const root = rootWithPendingPassiveEffects;
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);
}
}
function commitRoot(root) {
const previousPriority = getCurrentUpdatePriority();
try {
setCurrentUpdatePriority(DiscreteEventPriority);
commitRootImpl(root);
} finally {
setCurrentUpdatePriority(previousPriority);
}
}
function commitRootImpl(root) {
const { finishedWork } = root;
+ console.log('commit', finishedWork.child.memoizedState.memoizedState[0]);
root.callbackNode = null;
root.callbackPriority = NoLane;
+ const remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
+ markRootFinished(root, remainingLanes);
if ((finishedWork.subtreeFlags & Passive) !== NoFlags || (finishedWork.flags & Passive) !== NoFlags) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
Scheduler_scheduleCallback(NormalSchedulerPriority, flushPassiveEffects);
}
}
const subtreeHasEffects = (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
commitMutationEffects(finishedWork, root);
commitLayoutEffects(finishedWork, root);
root.current = finishedWork;
if (rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
}
}
root.current = finishedWork;
+ ensureRootIsScheduled(root, now());
}
function prepareFreshStack(root, lanes) {
workInProgressRoot = root;
workInProgress = createWorkInProgress(root.current, null);
workInProgressRootRenderLanes = lanes;
finishQueueingConcurrentUpdates();
}
function renderRootSync(root, lanes) {
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
prepareFreshStack(root, lanes)
}
workLoopSync();
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork, workInProgressRootRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
completeWork(current, completedWork);
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
if (workInProgressRootExitStatus === RootInProgress) {
workInProgressRootExitStatus = RootCompleted;
}
}
export function requestUpdateLane() {
const updateLane = getCurrentUpdatePriority();
if (updateLane !== NoLane) {
return updateLane;
}
const eventLane = getCurrentEventPriority();
return eventLane;
}
+export function requestEventTime() {
+ currentEventTime = now();
+ return currentEventTime;
+}
+function sleep(time) {
+ const timeStamp = new Date().getTime();
+ const endTime = timeStamp + time;
+ while (true) {
+ if (new Date().getTime() > endTime) {
+ return;
+ }
+ }
+}
src\react-reconciler\src\ReactFiberLane.js
import { allowConcurrentByDefault } from 'shared/ReactFeatureFlags';
+export const NoTimestamp = -1;
export const TotalLanes = 31;
export const NoLanes = 0b0000000000000000000000000000000;
export const NoLane = 0b0000000000000000000000000000000;
export const SyncLane = 0b0000000000000000000000000000001;// 1
export const InputContinuousLane = 0b0000000000000000000000000000100;// 4
export const DefaultLane = 0b0000000000000000000000000010000;// 16
export const NonIdleLanes = 0b0001111111111111111111111111111;
export const IdleLane = 0b0100000000000000000000000000000;
export function mergeLanes(a, b) {
return a | b;
}
export function markRootUpdated(root, updateLane) {
root.pendingLanes |= updateLane;
}
export function getNextLanes(root, wipLanes) {
const pendingLanes = root.pendingLanes;
if (pendingLanes === NoLanes) {
return NoLanes;
}
const nextLanes = getHighestPriorityLanes(pendingLanes);
if (wipLanes !== NoLanes && wipLanes !== nextLanes) {
if (nextLanes >= wipLanes) {
return wipLanes;
}
}
return nextLanes;
}
function getHighestPriorityLanes(lanes) {
return getHighestPriorityLane(lanes);
}
export function getHighestPriorityLane(lanes) {
return lanes & -lanes;
}
export function includesNonIdleWork(lanes) {
return (lanes & NonIdleLanes) !== NoLanes;
}
export function includesBlockingLane(root, lanes) {
if (allowConcurrentByDefault) {
return false;
}
const SyncDefaultLanes = InputContinuousLane | DefaultLane;
return (lanes & SyncDefaultLanes) !== NoLanes;
}
export function isSubsetOfLanes(set, subset) {
return (set & subset) === subset;
}
+function pickArbitraryLaneIndex(lanes) {
+ return 31 - Math.clz32(lanes);
+}
+
+export function markStarvedLanesAsExpired(root, currentTime) {
+ const pendingLanes = root.pendingLanes;
+ const expirationTimes = root.expirationTimes;
+ let lanes = pendingLanes
+ while (lanes > 0) {
+ const index = pickArbitraryLaneIndex(lanes);
+ const lane = 1 << index;
+ const expirationTime = expirationTimes[index];
+ if (expirationTime === NoTimestamp) {
+ expirationTimes[index] = computeExpirationTime(lane, currentTime);
+ } else if (expirationTime <= currentTime) {
+ root.expiredLanes |= lane;
+ }
+ lanes &= ~lane;
+ }
+}
+
+function computeExpirationTime(lane, currentTime) {
+ switch (lane) {
+ case SyncLane:
+ case InputContinuousLane:
+ return currentTime + 250;
+ case DefaultLane:
+ return currentTime + 5000;
+ case IdleLane:
+ return NoTimestamp;
+ default:
+ return NoTimestamp;
+ }
+}
+export function createLaneMap(initial) {
+ const laneMap = [];
+ for (let i = 0; i < TotalLanes; i++) {
+ laneMap.push(initial);
+ }
+ return laneMap;
+}
+export function includesExpiredLane(root, lanes) {
+ return (lanes & root.expiredLanes) !== NoLanes;
+}
+export function markRootFinished(root, remainingLanes) {
+ const noLongerPendingLanes = root.pendingLanes & ~remainingLanes;
+ root.pendingLanes = remainingLanes;
+ let lanes = noLongerPendingLanes;
+ const expirationTimes = root.expirationTimes;
+ while (lanes > 0) {
+ const index = pickArbitraryLaneIndex(lanes);
+ const lane = 1 << index;
+ expirationTimes[index] = NoTimestamp;
+ lanes &= ~lane;
+ }
+}
src\react-reconciler\src\ReactFiber.js
import { HostRoot, IndeterminateComponent, HostComponent, HostText } from "./ReactWorkTags";
import { NoFlags } from "./ReactFiberFlags";
+import { NoLanes } from './ReactFiberLane';
export function FiberNode(tag, pendingProps, key) {
this.tag = tag;
this.key = key;
this.type = null;
this.stateNode = null;
this.return = null;
this.child = null;
this.sibling = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
this.alternate = null;
this.index = 0;
this.ref = null;
+ this.lanes = NoLanes;
+ this.childLanes = NoLanes;
}
function createFiber(tag, pendingProps, key) {
return new FiberNode(tag, pendingProps, key);
}
export function createHostRootFiber() {
return createFiber(HostRoot, null, null);
}
// We use a double buffering pooling technique because we know that we'll
// only ever need at most two versions of a tree. We pool the "other" unused
// node that we're free to reuse. This is lazily created to avoid allocating
// extra objects for things that are never updated. It also allow us to
// reclaim the extra memory if needed.
//我们使用双缓冲池技术,因为我们知道一棵树最多只需要两个版本
//我们将“其他”未使用的我们可以自由重用的节点
//这是延迟创建的,以避免分配从未更新的内容的额外对象。它还允许我们如果需要,回收额外的内存
export function createWorkInProgress(current, pendingProps) {
let workInProgress = current.alternate;
if (workInProgress === null) {
workInProgress = createFiber(current.tag, pendingProps, current.key);
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
workInProgress.type = current.type;
workInProgress.flags = NoFlags;
workInProgress.subtreeFlags = NoFlags;
+ workInProgress.deletions = null;
}
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
+ workInProgress.flags = current.flags;
+ workInProgress.childLanes = current.childLanes;
+ workInProgress.lanes = current.lanes;
return workInProgress;
}
export function createFiberFromTypeAndProps(type, key, pendingProps) {
let fiberTag = IndeterminateComponent;
if (typeof type === "string") {
fiberTag = HostComponent;
}
const fiber = createFiber(fiberTag, pendingProps, key);
fiber.type = type;
return fiber;
}
export function createFiberFromElement(element) {
const { type } = element;
const { key } = element;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(type, key, pendingProps);
return fiber;
}
export function createFiberFromText(content) {
const fiber = createFiber(HostText, content, null);
return fiber;
}
src\react-reconciler\src\ReactFiberBeginWork.js
import { HostRoot, HostComponent, HostText, IndeterminateComponent, FunctionComponent } from "./ReactWorkTags";
import { processUpdateQueue, cloneUpdateQueue } from "./ReactFiberClassUpdateQueue";
import { mountChildFibers, reconcileChildFibers } from "./ReactChildFiber";
import { shouldSetTextContent } from "react-dom-bindings/src/client/ReactDOMHostConfig";
import { renderWithHooks } from "react-reconciler/src/ReactFiberHooks";
+import { NoLanes } from './ReactFiberLane';
function reconcileChildren(current, workInProgress, nextChildren) {
if (current === null) {
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren);
} else {
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren);
}
}
function updateHostRoot(current, workInProgress, renderLanes) {
const nextProps = workInProgress.pendingProps;
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, nextProps, renderLanes)
const nextState = workInProgress.memoizedState;
const nextChildren = nextState.element;
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
function updateHostComponent(current, workInProgress) {
const { type } = workInProgress;
const nextProps = workInProgress.pendingProps;
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
nextChildren = null;
}
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
function mountIndeterminateComponent(_current, workInProgress, Component) {
const props = workInProgress.pendingProps;
const value = renderWithHooks(null, workInProgress, Component, props);
workInProgress.tag = FunctionComponent;
reconcileChildren(null, workInProgress, value);
return workInProgress.child;
}
+function updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes) {
+ const nextChildren = renderWithHooks(current, workInProgress, Component, nextProps, renderLanes);
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
export function beginWork(current, workInProgress, renderLanes) {
+ workInProgress.lanes = NoLanes;
switch (workInProgress.tag) {
case IndeterminateComponent: {
return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
}
case FunctionComponent: {
const Component = workInProgress.type;
const resolvedProps = workInProgress.pendingProps;
+ return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderLanes);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
default:
return null;
}
}
src\react-reconciler\src\ReactFiberCompleteWork.js
import {
appendInitialChild,
createInstance,
createTextInstance,
finalizeInitialChildren,
prepareUpdate,
} from "react-dom-bindings/src/client/ReactDOMHostConfig";
import { HostComponent, HostRoot, HostText, FunctionComponent } from "./ReactWorkTags";
import { Ref, NoFlags, Update } from "./ReactFiberFlags";
+import { NoLanes, mergeLanes } from './ReactFiberLane';
function markRef(workInProgress) {
workInProgress.flags |= Ref;
}
function bubbleProperties(completedWork) {
+ let newChildLanes = NoLanes;
let subtreeFlags = NoFlags;
let child = completedWork.child;
while (child !== null) {
+ newChildLanes = mergeLanes(newChildLanes, mergeLanes(child.lanes, child.childLanes));
subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;
child = child.sibling;
}
+ completedWork.childLanes = newChildLanes;
completedWork.subtreeFlags |= subtreeFlags;
}
function appendAllChildren(parent, workInProgress) {
// 我们只有创建的顶级fiber,但需要递归其子节点来查找所有终端节点
let node = workInProgress.child;
while (node !== null) {
// 如果是原生节点,直接添加到父节点上
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
// 再看看第一个节节点是不是原生节点
} else if (node.child !== null) {
// node.child.return = node
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
// 如果没有弟弟就找父亲的弟弟
while (node.sibling === null) {
// 如果找到了根节点或者回到了原节点结束
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
// node.sibling.return = node.return
// 下一个弟弟节点
node = node.sibling;
}
}
function markUpdate(workInProgress) {
workInProgress.flags |= Update;
}
function updateHostComponent(current, workInProgress, type, newProps) {
const oldProps = current.memoizedProps;
const instance = workInProgress.stateNode;
const updatePayload = prepareUpdate(instance, type, oldProps, newProps);
workInProgress.updateQueue = updatePayload;
if (updatePayload) {
markUpdate(workInProgress);
}
}
export function completeWork(current, workInProgress) {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent: {
const { type } = workInProgress;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, type, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
const instance = createInstance(type, newProps, workInProgress);
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;
finalizeInitialChildren(instance, type, newProps);
if (workInProgress.ref !== null) {
markRef(workInProgress);
}
}
bubbleProperties(workInProgress);
return null;
}
case FunctionComponent:
bubbleProperties(workInProgress);
break;
case HostRoot:
bubbleProperties(workInProgress);
break;
case HostText: {
const newText = newProps;
workInProgress.stateNode = createTextInstance(newText);
bubbleProperties(workInProgress);
break;
}
default:
break;
}
}
src\react-reconciler\src\ReactFiberConcurrentUpdates.js
import { HostRoot } from "./ReactWorkTags";
+import { mergeLanes, NoLanes } from './ReactFiberLane';
const concurrentQueues = [];
let concurrentQueuesIndex = 0;
export function markUpdateLaneFromFiberToRoot(sourceFiber) {
let node = sourceFiber;
let parent = sourceFiber.return;
while (parent !== null) {
node = parent;
parent = parent.return;
}
if (node.tag === HostRoot) {
const root = node.stateNode;
return root;
}
return null;
}
export function enqueueConcurrentHookUpdate(fiber, queue, update, lane) {
enqueueUpdate(fiber, queue, update, lane);
return getRootForUpdatedFiber(fiber);
}
export function enqueueConcurrentClassUpdate(fiber, queue, update, lane) {
enqueueUpdate(fiber, queue, update, lane);
return getRootForUpdatedFiber(fiber);
}
function enqueueUpdate(fiber, queue, update, lane) {
concurrentQueues[concurrentQueuesIndex++] = fiber;
concurrentQueues[concurrentQueuesIndex++] = queue;
concurrentQueues[concurrentQueuesIndex++] = update;
concurrentQueues[concurrentQueuesIndex++] = lane;
+ fiber.lanes = mergeLanes(fiber.lanes, lane);
}
function getRootForUpdatedFiber(sourceFiber) {
let node = sourceFiber;
let parent = node.return;
while (parent !== null) {
node = parent;
parent = node.return;
}
return node.tag === HostRoot ? node.stateNode : null;
}
export function finishQueueingConcurrentUpdates() {
const endIndex = concurrentQueuesIndex;
concurrentQueuesIndex = 0;
let i = 0;
while (i < endIndex) {
const fiber = concurrentQueues[i++];
const queue = concurrentQueues[i++];
const update = concurrentQueues[i++];
const lane = concurrentQueues[i++]
if (queue !== null && update !== null) {
const pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
}
}
src\react-reconciler\src\ReactFiberHooks.js
import ReactSharedInternals from "shared/ReactSharedInternals";
import { enqueueConcurrentHookUpdate } from "./ReactFiberConcurrentUpdates";
+import { scheduleUpdateOnFiber, requestUpdateLane, requestEventTime } from "./ReactFiberWorkLoop";
import is from "shared/objectIs";
import { Passive as PassiveEffect, Update as UpdateEffect } from "./ReactFiberFlags";
import { HasEffect as HookHasEffect, Passive as HookPassive, Layout as HookLayout } from "./ReactHookEffectTags";
+import { NoLanes, NoLane, mergeLanes, isSubsetOfLanes } from './ReactFiberLane';
const { ReactCurrentDispatcher } = ReactSharedInternals;
let currentlyRenderingFiber = null;
let workInProgressHook = null;
let currentHook = null;
+let renderLanes = NoLanes;
const HooksDispatcherOnMountInDEV = {
useReducer: mountReducer,
useState: mountState,
useEffect: mountEffect,
useLayoutEffect: mountLayoutEffect,
useRef: mountRef,
};
const HooksDispatcherOnUpdateInDEV = {
useReducer: updateReducer,
useState: updateState,
useEffect: updateEffect,
useLayoutEffect: updateLayoutEffect,
useRef: updateRef
};
function mountRef(initialValue) {
const hook = mountWorkInProgressHook();
const ref = {
current: initialValue,
};
hook.memoizedState = ref;
return ref;
}
function updateRef() {
const hook = updateWorkInProgressHook();
return hook.memoizedState;
}
export function useLayoutEffect(reducer, initialArg) {
return ReactCurrentDispatcher.current.useLayoutEffect(reducer, initialArg);
}
function updateLayoutEffect(create, deps) {
return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}
function mountLayoutEffect(create, deps) {
const fiberFlags = UpdateEffect;
return mountEffectImpl(fiberFlags, HookLayout, create, deps);
}
function updateEffect(create, deps) {
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, destroy, nextDeps);
}
function areHookInputsEqual(nextDeps, prevDeps) {
if (prevDeps === null) {
return false;
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
function mountEffect(create, deps) {
return mountEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, undefined, nextDeps);
}
function pushEffect(tag, create, destroy, deps) {
const effect = {
tag,
create,
destroy,
deps,
next: null,
};
let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
function createFunctionComponentUpdateQueue() {
return {
lastEffect: null,
};
}
function basicStateReducer(state, action) {
return typeof action === "function" ? action(state) : action;
}
function mountReducer(reducer, initialArg) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialArg;
const queue = {
pending: null,
dispatch: null,
+ lastRenderedReducer: reducer,
+ lastRenderedState: initialArg
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function updateReducer(reducer) {
+ const hook = updateWorkInProgressHook();
+ const queue = hook.queue;
+ queue.lastRenderedReducer = reducer;
+ const current = currentHook;
+ let baseQueue = current.baseQueue;
+ const pendingQueue = queue.pending;
+ if (pendingQueue !== null) {
+ if (baseQueue !== null) {
+ const baseFirst = baseQueue.next;
+ const pendingFirst = pendingQueue.next;
+ baseQueue.next = pendingFirst;
+ pendingQueue.next = baseFirst;
+ }
+ current.baseQueue = baseQueue = pendingQueue;
+ queue.pending = null;
+ }
+ if (baseQueue !== null) {
+ printQueue(baseQueue)
+ const first = baseQueue.next;
+ let newState = current.baseState;
+ let newBaseState = null;
+ let newBaseQueueFirst = null;
+ let newBaseQueueLast = null;
+ let update = first;
+ do {
+ const updateLane = update.lane;
+ const shouldSkipUpdate = !isSubsetOfLanes(renderLanes, updateLane);
+ if (shouldSkipUpdate) {
+ const clone = {
+ lane: updateLane,
+ action: update.action,
+ hasEagerState: update.hasEagerState,
+ eagerState: update.eagerState,
+ next: null,
+ };
+ if (newBaseQueueLast === null) {
+ newBaseQueueFirst = newBaseQueueLast = clone;
+ newBaseState = newState;
+ } else {
+ newBaseQueueLast = newBaseQueueLast.next = clone;
+ }
+ currentlyRenderingFiber.lanes = mergeLanes(currentlyRenderingFiber.lanes, updateLane);
+ } else {
+ if (newBaseQueueLast !== null) {
+ const clone = {
+ lane: NoLane,
+ action: update.action,
+ hasEagerState: update.hasEagerState,
+ eagerState: update.eagerState,
+ next: null,
+ };
+ newBaseQueueLast = newBaseQueueLast.next = clone;
+ }
+ if (update.hasEagerState) {
+ newState = update.eagerState;
+ } else {
+ const action = update.action;
+ newState = reducer(newState, action);
+ }
+ }
+ update = update.next;
+ } while (update !== null && update !== first);
+ if (newBaseQueueLast === null) {
+ newBaseState = newState;
+ } else {
+ newBaseQueueLast.next = newBaseQueueFirst;
+ }
+ hook.memoizedState = newState;
+ hook.baseState = newBaseState;
+ hook.baseQueue = newBaseQueueLast;
+ queue.lastRenderedState = newState;
+ }
+ if (baseQueue === null) {
+ queue.lanes = NoLanes;
+ }
+ const dispatch = queue.dispatch;
+ return [hook.memoizedState, dispatch];
}
function mountState(initialState) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialState;
const queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function dispatchSetState(fiber, queue, action) {
const lane = requestUpdateLane(fiber);
const update = {
lane,
action,
hasEagerState: false,
eagerState: null,
next: null,
};
const alternate = fiber.alternate;
if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
const lastRenderedReducer = queue.lastRenderedReducer;
const currentState = queue.lastRenderedState;
const eagerState = lastRenderedReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
return;
}
}
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
+ const eventTime = requestEventTime();
+ scheduleUpdateOnFiber(root, fiber, lane, eventTime);
}
function updateState(initialState) {
return updateReducer(basicStateReducer, initialState);
}
function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
queue: null,
next: null,
+ baseState: null,
+ baseQueue: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function dispatchReducerAction(fiber, queue, action) {
const update = {
action,
next: null,
};
const root = enqueueConcurrentHookUpdate(fiber, queue, update);
scheduleUpdateOnFiber(root, fiber);
}
function updateWorkInProgressHook() {
let nextCurrentHook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
currentHook = nextCurrentHook;
const newHook = {
memoizedState: currentHook.memoizedState,
queue: currentHook.queue,
next: null,
+ baseState: currentHook.baseState,
+ baseQueue: currentHook.baseQueue,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
export function renderWithHooks(current, workInProgress, Component, props, nextRenderLanes) {
+ renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
workInProgress.updateQueue = null;
+ workInProgress.memoizedState = null;
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
const children = Component(props);
currentlyRenderingFiber = null;
workInProgressHook = null;
currentHook = null;
+ renderLanes = NoLanes;
return children;
}
+function printQueue(queue) {
+ const first = queue.next;
+ let desc = '';
+ let update = first;
+ do {
+ desc += ("=>" + (update.action.id));
+ update = update.next;
+ } while (update !== null && update !== first);
+ desc += "=>null";
+ console.log(desc);
+}
src\react-reconciler\src\ReactFiberReconciler.js
import { createFiberRoot } from "./ReactFiberRoot";
import { createUpdate, enqueueUpdate } from "./ReactFiberClassUpdateQueue";
+import { scheduleUpdateOnFiber, requestUpdateLane, requestEventTime } from "./ReactFiberWorkLoop";
export function createContainer(containerInfo) {
return createFiberRoot(containerInfo);
}
export function updateContainer(element, container) {
const current = container.current;
+ const eventTime = requestEventTime();
const lane = requestUpdateLane(current);
const update = createUpdate(lane);
update.payload = { element };
const root = enqueueUpdate(current, update, lane);
+ scheduleUpdateOnFiber(root, current, lane, eventTime);
}
src\react-reconciler\src\ReactFiberRoot.js
import { createHostRootFiber } from "./ReactFiber";
import { initializeUpdateQueue } from "./ReactFiberClassUpdateQueue";
+import { NoTimestamp, createLaneMap, NoLanes } from 'react-reconciler/src/ReactFiberLane';
function FiberRootNode(containerInfo) {
this.containerInfo = containerInfo;
+ this.expirationTimes = createLaneMap(NoTimestamp);
+ this.expiredLanes = NoLanes;
}
export function createFiberRoot(containerInfo) {
const root = new FiberRootNode(containerInfo);
const uninitializedFiber = createHostRootFiber();
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
initializeUpdateQueue(uninitializedFiber);
return root;
}
src\main.jsx
import * as React from "react";
import { createRoot } from "react-dom/client";
+const NameContext = React.createContext('');
+const AgeContext = React.createContext('');
+
+function Child() {
+ const name = React.useContext(NameContext);
+ const age = React.useContext(AgeContext);
+ return <button>{name + age}</button>
+}
+function App() {
+ const [name, setName] = React.useState('a');
+ const [age, setAge] = React.useState('1');
+ return (
+ <div>
+ <button onClick={() => {
+ setName(name + 'a')
+ }}>setName</button>
+ <button onClick={() => {
+ setAge(age + '1')
+ }}>setAge</button>
+ <NameContext.Provider value={name}>
+ <AgeContext.Provider value={age}>
+ <Child />
+ </AgeContext.Provider>
+ </NameContext.Provider>
+ </div>
+ )
+}
const element = <App />;
const container = document.getElementById("root");
const root = createRoot(container, { unstable_concurrentUpdatesByDefault: true });
root.render(element);
src\react\index.js
export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
useReducer,
useState,
useEffect,
useLayoutEffect,
useRef,
+ createContext,
+ useContext,
} from "./src/React";
src\react\src\React.js
+import { useReducer, useState, useEffect, useLayoutEffect, useRef, useContext } from "./ReactHooks";
import ReactSharedInternals from "./ReactSharedInternals";
+import { createContext } from './ReactContext';
export {
useReducer,
useState,
useEffect,
useLayoutEffect,
useRef,
+ createContext,
+ useContext,
ReactSharedInternals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
};
src\react\src\ReactContext.js
import { REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE } from 'shared/ReactSymbols';
export function createContext(defaultValue) {
const context = {
$$typeof: REACT_CONTEXT_TYPE,
_currentValue: defaultValue,
Provider: null
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context
};
return context;
}
src\react\src\ReactHooks.js
import ReactCurrentDispatcher from "./ReactCurrentDispatcher";
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return dispatcher;
}
export function useReducer(reducer, initialArg, init) {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg, init);
}
export function useState(initialState) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
export function useEffect(create, deps) {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, deps);
}
export function useLayoutEffect(create, deps) {
const dispatcher = resolveDispatcher();
return dispatcher.useLayoutEffect(create, deps);
}
export function useRef(initialValue) {
const dispatcher = resolveDispatcher();
return dispatcher.useRef(initialValue);
}
+export function useContext(Context) {
+ const dispatcher = resolveDispatcher();
+ return dispatcher.useContext(Context);
+}
src\react-reconciler\src\ReactFiber.js
+import { HostRoot, IndeterminateComponent, HostComponent, HostText, ContextProvider } from "./ReactWorkTags";
import { NoFlags } from "./ReactFiberFlags";
import { NoLanes } from './ReactFiberLane';
+import { REACT_PROVIDER_TYPE } from 'shared/ReactSymbols';
export function FiberNode(tag, pendingProps, key) {
this.tag = tag;
this.key = key;
this.type = null;
this.stateNode = null;
this.return = null;
this.child = null;
this.sibling = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
this.alternate = null;
this.index = 0;
this.ref = null;
this.lanes = NoLanes;
this.childLanes = NoLanes;
}
function createFiber(tag, pendingProps, key) {
return new FiberNode(tag, pendingProps, key);
}
export function createHostRootFiber() {
return createFiber(HostRoot, null, null);
}
// We use a double buffering pooling technique because we know that we'll
// only ever need at most two versions of a tree. We pool the "other" unused
// node that we're free to reuse. This is lazily created to avoid allocating
// extra objects for things that are never updated. It also allow us to
// reclaim the extra memory if needed.
//我们使用双缓冲池技术,因为我们知道一棵树最多只需要两个版本
//我们将“其他”未使用的我们可以自由重用的节点
//这是延迟创建的,以避免分配从未更新的内容的额外对象。它还允许我们如果需要,回收额外的内存
export function createWorkInProgress(current, pendingProps) {
let workInProgress = current.alternate;
if (workInProgress === null) {
workInProgress = createFiber(current.tag, pendingProps, current.key);
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
workInProgress.type = current.type;
workInProgress.flags = NoFlags;
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
}
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
workInProgress.flags = current.flags;
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
return workInProgress;
}
export function createFiberFromTypeAndProps(type, key, pendingProps) {
let fiberTag = IndeterminateComponent;
if (typeof type === "string") {
fiberTag = HostComponent;
} else {
+ getTag: switch (type) {
+ default:
+ {
+ if (typeof type === 'object' && type !== null) {
+ switch (type.$$typeof) {
+ case REACT_PROVIDER_TYPE:
+ fiberTag = ContextProvider;
+ break getTag;
+ default:
+ break;
+ }
+ }
+ }
+ }
}
const fiber = createFiber(fiberTag, pendingProps, key);
fiber.type = type;
return fiber;
}
export function createFiberFromElement(element) {
const { type } = element;
const { key } = element;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(type, key, pendingProps);
return fiber;
}
export function createFiberFromText(content) {
const fiber = createFiber(HostText, content, null);
return fiber;
}
src\react-reconciler\src\ReactFiberBeginWork.js
import {
HostRoot, HostComponent, HostText, IndeterminateComponent,
+ FunctionComponent, ContextProvider
} from "./ReactWorkTags";
import { processUpdateQueue, cloneUpdateQueue } from "./ReactFiberClassUpdateQueue";
import { mountChildFibers, reconcileChildFibers } from "./ReactChildFiber";
import { shouldSetTextContent } from "react-dom-bindings/src/client/ReactDOMHostConfig";
import { renderWithHooks } from "react-reconciler/src/ReactFiberHooks";
import { NoLanes } from './ReactFiberLane';
+import { pushProvider } from './ReactFiberNewContext';
function reconcileChildren(current, workInProgress, nextChildren) {
if (current === null) {
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren);
} else {
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren);
}
}
function updateHostRoot(current, workInProgress, renderLanes) {
const nextProps = workInProgress.pendingProps;
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, nextProps, renderLanes)
const nextState = workInProgress.memoizedState;
const nextChildren = nextState.element;
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
function updateHostComponent(current, workInProgress) {
const { type } = workInProgress;
const nextProps = workInProgress.pendingProps;
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
nextChildren = null;
}
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
function mountIndeterminateComponent(_current, workInProgress, Component) {
const props = workInProgress.pendingProps;
const value = renderWithHooks(null, workInProgress, Component, props);
workInProgress.tag = FunctionComponent;
reconcileChildren(null, workInProgress, value);
return workInProgress.child;
}
function updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes) {
const nextChildren = renderWithHooks(current, workInProgress, Component, nextProps, renderLanes);
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
export function beginWork(current, workInProgress, renderLanes) {
workInProgress.lanes = NoLanes;
switch (workInProgress.tag) {
case IndeterminateComponent: {
return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
}
case FunctionComponent: {
const Component = workInProgress.type;
const resolvedProps = workInProgress.pendingProps;
return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderLanes);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return null;
+ case ContextProvider:
+ return updateContextProvider(current, workInProgress, renderLanes);
default:
return null;
}
}
+function updateContextProvider(current, workInProgress, renderLanes) {
+ const providerType = workInProgress.type;
+ const context = providerType._context;
+ const newProps = workInProgress.pendingProps;
+ const newValue = newProps.value;
+ pushProvider(context, newValue);
+ const newChildren = newProps.children;
+ reconcileChildren(current, workInProgress, newChildren, renderLanes);
+ return workInProgress.child;
+}
src\react-reconciler\src\ReactFiberCompleteWork.js
import {
appendInitialChild,
createInstance,
createTextInstance,
finalizeInitialChildren,
prepareUpdate,
} from "react-dom-bindings/src/client/ReactDOMHostConfig";
+import { HostComponent, HostRoot, HostText, FunctionComponent, ContextProvider } from "./ReactWorkTags";
import { Ref, NoFlags, Update } from "./ReactFiberFlags";
import { NoLanes, mergeLanes } from './ReactFiberLane';
function markRef(workInProgress) {
workInProgress.flags |= Ref;
}
function bubbleProperties(completedWork) {
let newChildLanes = NoLanes;
let subtreeFlags = NoFlags;
let child = completedWork.child;
while (child !== null) {
newChildLanes = mergeLanes(newChildLanes, mergeLanes(child.lanes, child.childLanes));
subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;
child = child.sibling;
}
completedWork.childLanes = newChildLanes;
completedWork.subtreeFlags |= subtreeFlags;
}
function appendAllChildren(parent, workInProgress) {
// 我们只有创建的顶级fiber,但需要递归其子节点来查找所有终端节点
let node = workInProgress.child;
while (node !== null) {
// 如果是原生节点,直接添加到父节点上
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
// 再看看第一个节节点是不是原生节点
} else if (node.child !== null) {
// node.child.return = node
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
// 如果没有弟弟就找父亲的弟弟
while (node.sibling === null) {
// 如果找到了根节点或者回到了原节点结束
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
// node.sibling.return = node.return
// 下一个弟弟节点
node = node.sibling;
}
}
function markUpdate(workInProgress) {
workInProgress.flags |= Update;
}
function updateHostComponent(current, workInProgress, type, newProps) {
const oldProps = current.memoizedProps;
const instance = workInProgress.stateNode;
const updatePayload = prepareUpdate(instance, type, oldProps, newProps);
workInProgress.updateQueue = updatePayload;
if (updatePayload) {
markUpdate(workInProgress);
}
}
export function completeWork(current, workInProgress) {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent: {
const { type } = workInProgress;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, type, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
const instance = createInstance(type, newProps, workInProgress);
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;
finalizeInitialChildren(instance, type, newProps);
if (workInProgress.ref !== null) {
markRef(workInProgress);
}
}
bubbleProperties(workInProgress);
return null;
}
case FunctionComponent:
bubbleProperties(workInProgress);
break;
case HostRoot:
bubbleProperties(workInProgress);
break;
case HostText: {
const newText = newProps;
workInProgress.stateNode = createTextInstance(newText);
bubbleProperties(workInProgress);
break;
}
+ case ContextProvider: {
+ bubbleProperties(workInProgress);
+ break;
+ }
default:
break;
}
}
src\react-reconciler\src\ReactFiberCommitWork.js
+import { HostRoot, HostComponent, HostText, FunctionComponent, ContextProvider } from "./ReactWorkTags";
import { Passive, MutationMask, Placement, Update, LayoutMask, Ref } from "./ReactFiberFlags";
import { insertBefore, appendChild, commitUpdate, removeChild } from "react-dom-bindings/src/client/ReactDOMHostConfig";
import { HasEffect as HookHasEffect, Passive as HookPassive, Layout as HookLayout } from "./ReactHookEffectTags";
export function commitMutationEffects(finishedWork, root) {
commitMutationEffectsOnFiber(finishedWork, root);
}
export function commitPassiveUnmountEffects(finishedWork) {
commitPassiveUnmountOnFiber(finishedWork);
}
function commitPassiveUnmountOnFiber(finishedWork) {
switch (finishedWork.tag) {
case FunctionComponent: {
recursivelyTraversePassiveUnmountEffects(finishedWork);
if (finishedWork.flags & Passive) {
commitHookPassiveUnmountEffects(finishedWork, finishedWork.return, HookPassive | HookHasEffect);
}
break;
}
default: {
recursivelyTraversePassiveUnmountEffects(finishedWork);
break;
}
}
}
function recursivelyTraversePassiveUnmountEffects(parentFiber) {
if (parentFiber.subtreeFlags & Passive) {
let child = parentFiber.child;
while (child !== null) {
commitPassiveUnmountOnFiber(child);
child = child.sibling;
}
}
}
function commitHookPassiveUnmountEffects(finishedWork, nearestMountedAncestor, hookFlags) {
commitHookEffectListUnmount(hookFlags, finishedWork, nearestMountedAncestor);
}
function commitHookEffectListUnmount(flags, finishedWork) {
const updateQueue = finishedWork.updateQueue;
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
destroy();
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
export function commitPassiveMountEffects(root, finishedWork) {
commitPassiveMountOnFiber(root, finishedWork);
}
function commitPassiveMountOnFiber(finishedRoot, finishedWork) {
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent: {
recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork);
if (flags & Passive) {
commitHookPassiveMountEffects(finishedWork, HookPassive | HookHasEffect);
}
break;
}
case HostRoot: {
recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork);
break;
}
default:
break;
}
}
function commitHookPassiveMountEffects(finishedWork, hookFlags) {
commitHookEffectListMount(hookFlags, finishedWork);
}
function commitHookEffectListMount(flags, finishedWork) {
const updateQueue = finishedWork.updateQueue;
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
const create = effect.create;
effect.destroy = create();
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
function recursivelyTraversePassiveMountEffects(root, parentFiber) {
if (parentFiber.subtreeFlags & Passive) {
let child = parentFiber.child;
while (child !== null) {
commitPassiveMountOnFiber(root, child);
child = child.sibling;
}
}
}
let hostParent = null;
function commitDeletionEffects(root, returnFiber, deletedFiber) {
let parent = returnFiber;
findParent: while (parent !== null) {
switch (parent.tag) {
case HostComponent: {
hostParent = parent.stateNode;
break findParent;
}
case HostRoot: {
hostParent = parent.stateNode.containerInfo;
break findParent;
}
default:
break;
}
parent = parent.return;
}
commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
hostParent = null;
}
function commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, deletedFiber) {
switch (deletedFiber.tag) {
case HostComponent:
case HostText: {
recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
if (hostParent !== null) {
removeChild(hostParent, deletedFiber.stateNode);
}
break;
}
default:
break;
}
}
function recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, parent) {
let child = parent.child;
while (child !== null) {
commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);
child = child.sibling;
}
}
function recursivelyTraverseMutationEffects(root, parentFiber) {
const deletions = parentFiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
commitDeletionEffects(root, parentFiber, childToDelete);
}
}
if (parentFiber.subtreeFlags & MutationMask) {
let { child } = parentFiber;
while (child !== null) {
commitMutationEffectsOnFiber(child, root);
child = child.sibling;
}
}
}
function isHostParent(fiber) {
return fiber.tag === HostComponent || fiber.tag === HostRoot;
}
function getHostParentFiber(fiber) {
let parent = fiber.return;
while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}
parent = parent.return;
}
return parent;
}
function insertOrAppendPlacementNode(node, before, parent) {
const { tag } = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost) {
const { stateNode } = node;
if (before) {
insertBefore(parent, stateNode, before);
} else {
appendChild(parent, stateNode);
}
} else {
const { child } = node;
if (child !== null) {
insertOrAppendPlacementNode(child, before, parent);
let { sibling } = child;
while (sibling !== null) {
insertOrAppendPlacementNode(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}
function getHostSibling(fiber) {
let node = fiber;
siblings: while (true) {
// 如果我们没有找到任何东西,让我们试试下一个弟弟
while (node.sibling === null) {
if (node.return === null || isHostParent(node.return)) {
// 如果我们是根Fiber或者父亲是原生节点,我们就是最后的弟弟
return null;
}
node = node.return;
}
// node.sibling.return = node.return
node = node.sibling;
while (node.tag !== HostComponent && node.tag !== HostText) {
// 如果它不是原生节点,并且,我们可能在其中有一个原生节点
// 试着向下搜索,直到找到为止
if (node.flags & Placement) {
// 如果我们没有孩子,可以试试弟弟
continue siblings;
} else {
// node.child.return = node
node = node.child;
}
} // Check if this host node is stable or about to be placed.
// 检查此原生节点是否稳定可以放置
if (!(node.flags & Placement)) {
// 找到它了!
return node.stateNode;
}
}
}
function commitPlacement(finishedWork) {
const parentFiber = getHostParentFiber(finishedWork);
switch (parentFiber.tag) {
case HostComponent: {
const parent = parentFiber.stateNode;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
case HostRoot: {
const parent = parentFiber.stateNode.containerInfo;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
default:
break;
}
}
function commitReconciliationEffects(finishedWork) {
const { flags } = finishedWork;
if (flags & Placement) {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}
}
export function commitMutationEffectsOnFiber(finishedWork, root) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case HostRoot: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
case FunctionComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork, finishedWork.return);
}
break;
}
case HostComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Ref) {
commitAttachRef(finishedWork);
}
if (flags & Update) {
const instance = finishedWork.stateNode;
if (instance != null) {
const newProps = finishedWork.memoizedProps;
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
const updatePayload = finishedWork.updateQueue;
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(instance, updatePayload, type, oldProps, newProps, finishedWork);
}
}
}
break;
}
case HostText: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
break;
}
+ case ContextProvider: {
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ commitReconciliationEffects(finishedWork);
+ break;
}
default: {
break;
}
}
}
function commitAttachRef(finishedWork) {
const ref = finishedWork.ref;
if (ref !== null) {
const instance = finishedWork.stateNode;
if (typeof ref === "function") {
ref(instance)
} else {
ref.current = instance;
}
}
}
export function commitLayoutEffects(finishedWork, root) {
const current = finishedWork.alternate;
commitLayoutEffectOnFiber(root, current, finishedWork);
}
function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork) {
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent: {
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
if (flags & Update) {
commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);
}
break;
}
case HostRoot: {
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
break;
}
default:
break;
}
}
function recursivelyTraverseLayoutEffects(root, parentFiber) {
if (parentFiber.subtreeFlags & LayoutMask) {
let child = parentFiber.child;
while (child !== null) {
const current = child.alternate;
commitLayoutEffectOnFiber(root, current, child);
child = child.sibling;
}
}
}
function commitHookLayoutEffects(finishedWork, hookFlags) {
commitHookEffectListMount(hookFlags, finishedWork);
}
src\react-reconciler\src\ReactFiberHooks.js
import ReactSharedInternals from "shared/ReactSharedInternals";
import { enqueueConcurrentHookUpdate } from "./ReactFiberConcurrentUpdates";
import { scheduleUpdateOnFiber, requestUpdateLane, requestEventTime } from "./ReactFiberWorkLoop";
import is from "shared/objectIs";
import { Passive as PassiveEffect, Update as UpdateEffect } from "./ReactFiberFlags";
import { HasEffect as HookHasEffect, Passive as HookPassive, Layout as HookLayout } from "./ReactHookEffectTags";
import { NoLanes, NoLane, mergeLanes, isSubsetOfLanes } from './ReactFiberLane';
+import { readContext } from './ReactFiberNewContext';
const { ReactCurrentDispatcher } = ReactSharedInternals;
let currentlyRenderingFiber = null;
let workInProgressHook = null;
let currentHook = null;
let renderLanes = NoLanes;
const HooksDispatcherOnMountInDEV = {
useReducer: mountReducer,
useState: mountState,
useEffect: mountEffect,
useLayoutEffect: mountLayoutEffect,
useRef: mountRef,
+ useContext: readContext
};
const HooksDispatcherOnUpdateInDEV = {
useReducer: updateReducer,
useState: updateState,
useEffect: updateEffect,
useLayoutEffect: updateLayoutEffect,
useRef: updateRef,
+ useContext: readContext
};
function mountRef(initialValue) {
const hook = mountWorkInProgressHook();
const ref = {
current: initialValue,
};
hook.memoizedState = ref;
return ref;
}
function updateRef() {
const hook = updateWorkInProgressHook();
return hook.memoizedState;
}
export function useLayoutEffect(reducer, initialArg) {
return ReactCurrentDispatcher.current.useLayoutEffect(reducer, initialArg);
}
function updateLayoutEffect(create, deps) {
return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}
function mountLayoutEffect(create, deps) {
const fiberFlags = UpdateEffect;
return mountEffectImpl(fiberFlags, HookLayout, create, deps);
}
function updateEffect(create, deps) {
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, destroy, nextDeps);
}
function areHookInputsEqual(nextDeps, prevDeps) {
if (prevDeps === null) {
return false;
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
function mountEffect(create, deps) {
return mountEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, undefined, nextDeps);
}
function pushEffect(tag, create, destroy, deps) {
const effect = {
tag,
create,
destroy,
deps,
next: null,
};
let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
function createFunctionComponentUpdateQueue() {
return {
lastEffect: null,
};
}
function basicStateReducer(state, action) {
return typeof action === "function" ? action(state) : action;
}
function mountReducer(reducer, initialArg) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialArg;
const queue = {
pending: null,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: initialArg
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function updateReducer(reducer) {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const current = currentHook;
let baseQueue = current.baseQueue;
const pendingQueue = queue.pending;
if (pendingQueue !== null) {
if (baseQueue !== null) {
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
if (baseQueue !== null) {
printQueue(baseQueue)
const first = baseQueue.next;
let newState = current.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first;
do {
const updateLane = update.lane;
const shouldSkipUpdate = !isSubsetOfLanes(renderLanes, updateLane);
if (shouldSkipUpdate) {
const clone = {
lane: updateLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: null,
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
currentlyRenderingFiber.lanes = mergeLanes(currentlyRenderingFiber.lanes, updateLane);
} else {
if (newBaseQueueLast !== null) {
const clone = {
lane: NoLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: null,
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}
if (update.hasEagerState) {
newState = update.eagerState;
} else {
const action = update.action;
newState = reducer(newState, action);
}
}
update = update.next;
} while (update !== null && update !== first);
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = newBaseQueueFirst;
}
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
if (baseQueue === null) {
queue.lanes = NoLanes;
}
const dispatch = queue.dispatch;
return [hook.memoizedState, dispatch];
}
function mountState(initialState) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialState;
const queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function dispatchSetState(fiber, queue, action) {
const lane = requestUpdateLane(fiber);
const update = {
lane,
action,
hasEagerState: false,
eagerState: null,
next: null,
};
const alternate = fiber.alternate;
if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
const lastRenderedReducer = queue.lastRenderedReducer;
const currentState = queue.lastRenderedState;
const eagerState = lastRenderedReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
return;
}
}
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
}
function updateState(initialState) {
return updateReducer(basicStateReducer, initialState);
}
function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
queue: null,
next: null,
baseState: null,
baseQueue: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function dispatchReducerAction(fiber, queue, action) {
const update = {
action,
next: null,
};
const root = enqueueConcurrentHookUpdate(fiber, queue, update);
scheduleUpdateOnFiber(root, fiber);
}
function updateWorkInProgressHook() {
let nextCurrentHook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
currentHook = nextCurrentHook;
const newHook = {
memoizedState: currentHook.memoizedState,
queue: currentHook.queue,
next: null,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
export function renderWithHooks(current, workInProgress, Component, props, nextRenderLanes) {
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
workInProgress.updateQueue = null;
workInProgress.memoizedState = null;
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
const children = Component(props);
currentlyRenderingFiber = null;
workInProgressHook = null;
currentHook = null;
renderLanes = NoLanes;
return children;
}
function printQueue(queue) {
const first = queue.next;
let desc = '';
let update = first;
do {
desc += ("=>" + (update.action.id));
update = update.next;
} while (update !== null && update !== first);
desc += "=>null";
console.log(desc);
}
src\react-reconciler\src\ReactFiberNewContext.js
export function pushProvider(context, nextValue) {
context._currentValue = nextValue;
}
export function readContext(context) {
return context._currentValue;
}
src\react-reconciler\src\ReactWorkTags.js
export const FunctionComponent = 0;
export const IndeterminateComponent = 2;
export const HostRoot = 3;
export const HostComponent = 5;
export const HostText = 6;
+export const ContextProvider = 10;
src\shared\ReactSymbols.js
export const REACT_ELEMENT_TYPE = Symbol.for("react.element");
+export const REACT_PROVIDER_TYPE = Symbol.for('react.provider');
+export const REACT_CONTEXT_TYPE = Symbol.for('react.context');