fre 中的结构和算法

最近考研复试临近,实在很紧张,面试要提供简历和资料,时间很短,俺只能给老师们看一下 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++;
    }
}

总结

以上,实际算法实现要复杂一些,仅供参考,工作也好,面试也好,学术研究也好,希望俺的思路也能帮到大家,如果大家有更好的思路也可以发俺哈

相关推荐
liliangcsdn4 分钟前
mac mlx大模型框架的安装和使用
java·前端·人工智能·python·macos
CssHero7 分钟前
基于vue3完成领域模型架构建设
前端
PanZonghui10 分钟前
用项目说话:我的React博客构建成果与经验复盘
前端·react.js·typescript
言兴13 分钟前
教你如何理解useContext加上useReducer
前端·javascript·面试
sunbyte17 分钟前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | GoodCheapFast(Good - Cheap - Fast三选二开关)
前端·javascript·css·vue.js·tailwindcss
前端的日常18 分钟前
网页视频录制新技巧,代码实现超简单!
前端
前端的日常20 分钟前
什么是 TypeScript 中的泛型?请给出一个使用泛型的示例。
前端
今禾21 分钟前
一行代码引发的血案:new Array(5) 到底发生了什么?
前端·javascript·算法
ccc101824 分钟前
老师问我localhost和127.0.0.1,有什么区别?
前端
Struggler28131 分钟前
Chrome插件开发
前端