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++;
    }
}

总结

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

相关推荐
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁1 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95271 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大1 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师1 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学1 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端