最近考研复试临近,实在很紧张,面试要提供简历和资料,时间很短,俺只能给老师们看一下 fre 了,所以单独写一篇文章
fre 是一个 react-like 框架,使用了 560 行代码复刻了 react 异步渲染,总体上就一个 fiber 结构和一个 reconcile 算法
fiber 结构
fiber 结构本质上是使用一个三个指针的链表来描述 dom 结构,在这个链表的基础上进行深度遍历,目的是为了做异步渲染(时间切片,suspense)
js
let root = fiber
let node = fiber
while (true) {
if (node.child) {
node = node.child
continue
}
if (node === root) return
while (!node.sibling) {
if (!node.parent || node.parent=== root) return
node = node.parent
}
node = node.sibling
}
传统的 diff 算法,都是基于数组的同步 diff 算法,拿 vue3、inferno 为例,本质上是寻找一个"最长公共子序列",然后保持不动,剩下的进行移动操作,而最长公共子序列可以转换为最长递增子序列问题,进而使用二分查找将复杂度优化到O(nlogn)
聪明的人已经发现了,传统的基于 LCS/LIS 的算法,是无法用到 fiber 结构里的,甚至同样的思路都不适用于链表,因为链表是个线性结构,它的遍历限制了算法的发挥(只能从头到尾 onepass)
因为我不得不思考一个新的适用于链表的 onepass 的 reconcile 算法
在 fiber 中实现最长移动
我尝试过各种算法以后,想到一种方法,既然我们没办法在链表中寻找最长公共子序列,那么我们就寻找最长移动节点
js
[1, 2, 3, 4, 5] => [5, 1, 2, 3, 4] // 5 从 4 到了 0,明显的最大移动距离,因为我们就只移动 5
这个思路可能不能总是寻找到最短编辑距离,但大多数时候可以保持 onepass,可以和 fiber 结构结合起来,最好复杂度甚至可以做到 O(n)
js
function reconcile(bList, view, x) {
let currentA = aList.head
let currentB = bList.head
let position = 0
while (currentA || currentB) {
if (!currentB) {
// 删除多余节点
removeNode(currentA)
currentA = currentA.next
continue
}
if (!currentA) {
// 插入新节点
insertNode(currentB, null)
currentB = currentB.next
continue
}
const bKey = currentB.key
const aKey = currentA.key
if (aKey === bKey) {
// 更新节点
updateNode(currentA, currentB, view)
currentA = currentA.next
currentB = currentB.next
} else {
// 寻找最长距离
let foundA = this.findKeyInRemaining(aList, currentA.next, bKey)
let foundB = this.findKeyInRemaining(bList, currentB.next, aKey)
if (foundA && foundB) {
// 比较移动距离
if (foundA.distance <= foundB.distance) {
// 移动A链表节点
moveNodeBefore(foundA.node, currentA)
currentA = foundA.node;
} else {
// 移动B链表节点
insertNode(foundB.node, currentA)
currentB = foundB.node.next
}
} else if (foundA) {
// 移动A链表节点
moveNodeBefore(foundA.node, currentA);
currentA = foundA.node
} else {
// 新增B链表节点
this.insertNode(currentB, currentA)
currentB = currentB.next
}
}
position++;
}
}
总结
以上,实际算法实现要复杂一些,仅供参考,工作也好,面试也好,学术研究也好,希望俺的思路也能帮到大家,如果大家有更好的思路也可以发俺哈