1.为什么要选择链表结构遍历组件树?
深度优先:
var C = {
key: 'A1',
children: [
{
key: "B1",
children: [{
key: "c1", children: [],
},
{
key: "c2", children: []
}]
}, {
key: 'B2', children: []
}
]
}
function walk(vdom) {
doWork(vdom);
vdom.children.forEach(child => {
walk(child)
});
}
walk(C)
function doWork(vdom) {
console.log(vdom.key, '---key---')
//返回 A1 B1 C1 c2 B2
}
链表结构可以避免递归,递归无法停止(直接会执行到底),中断,溯源,并且一旦嵌套层级多,当前任务量增大,很容易卡住,为了契合使用requestIdleCallback优化方案。 必须要让任务能中断。
例: 更新 使用链表结构更新 setState:
class Update { //payload 数据或者元素
constructor(payload, nextUpdate) {
this.payload = payload;
this.nextUpdate = nextUpdate; //指向下一个节点的指针
}
}
class UpdateQueue {
constructor() {
this.baseState = null; //原状态
this.firstUpdate = null; //第一个更新
this.lastUpdate = null; //最后一个更新
}
enqueueUpdate(update) {
if (this.firstUpdate == null) {
this.firstUpdate = this.lastUpdate = update;
//这里是初始化的时候, 初始状态和最后一个状态一起指向第一个、
} else {
this.lastUpdate.nextUpdate = update; //上一个最后一个节点nextUpdate 指向自己
this.lastUpdate = update; //让最后一个节点指向自己
}
}
//通过这个把setState 串联成一个链表。
forceUpdate() {
let currentState = this.baseState || {};
let currentUpdate = this.firstUpdate;
while (currentUpdate) {
let nextState = typeof currentUpdate.payload == 'function'
? currentUpdate.payload(currentState) : currentUpdate.payload;
currentState = { ...currentState, ...nextState }; //使用当前更新得到新的状态
//使用当前更新得到新的状态
currentUpdate = currentUpdate.nextUpdate; // 找下一个节点
}
this.firstUpdate = this.lastUpdate = null;
// 更新完之后要把链表清空
this.baseState = currentState;
return currentState;
}
}
let queue = new UpdateQueue();
queue.enqueueUpdate(new Update({ name: 'lianjie' }));
queue.enqueueUpdate(new Update({ number: 0 }));
queue.enqueueUpdate(new Update((state) => { return { number: state.number + 1 } }));
queue.enqueueUpdate(new Update((state) => { return { number: state.number + 1 } }));
queue.forceUpdate();
可以看到结果已经生成。
2.实际React 链表结构
A1 这个节点包含三个指针,return,sibling,child.
注意: return的永远是父级的第一个节点。 C2->B1 B2->A1。
在查找结构中:
绿色的线代表执行的顺序,也就是会先深度优先遍历。找到最深的孩子之后(C1,没有child终止),再看这个孩子有没有兄弟,有兄弟就继续sibling,直到C2没有sibling,之后再return回上一级的直接父级。
顺序 : A1-> B1 -> C1 -> C2 -> B1-> B2 ->A1
整体就像在画三角形, 比如(B1 -> c1 -> c2 -> B1)。
我们用简单实例说明:
let A1 = { type: "div", key: "A1" };
let B1 = { type: "div", key: "B1", return: A1 };
let B2 = { type: "div", key: "B2", return: A1 };
let C1 = { type: "div", key: "C1", return: B1 };
let C2 = { type: "div", key: "C2", return: B1 };
A1.child = B1;
B1.sibling = B2;
B1.child = C1;
C1.sibling = C2;
上述代码用来描述上图结构关系。
...
let root = A1; //保存头部节点
let nextUnitFWork = null; //表示当前的fiber节点
function workLoop(deadline) {
//这里承接我第一篇提到的空闲时间>0 才执行,不然走下一帧
while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && nextUnitFWork) {
nextUnitFWork = performUnitOfWork(nextUnitFWork);
}
if (nextUnitFWork) {
window.requestIdleCallback(workLoop, { timeout: 1000 })
}
}
function performUnitOfWork(fiber) { //A1
beginWork(fiber); //处理此fiber
if (fiber.child) { //如果有大儿子 返回 ,一级一级找到最深的那个
return fiber.child;
}
while (fiber) {
//最深的儿子找到之后 再找兄弟
completeUnitOfWork(fiber);
if (fiber.sibling) {
return fiber.sibling; //如果有兄弟返回兄弟
}
fiber = fiber.return; //没有兄弟之后返回父级再循环找
}
}
function beginWork(fiber) {
console.log("开始", fiber.key); //处理fiber
}
function completeUnitOfWork(fiber) {
console.log(fiber.key, '结束'); //处理fiber
}
nextUnitFWork = A1;
window.requestIdleCallback(workLoop, { timeout: 1000 }) //第一次执行
返回结果: