V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
chenchunhan8888
V2EX  ›  Vue.js

请教一个 vue3 diff 问题

  •  
  •   chenchunhan8888 · 37 天前 · 1074 次点击
    这是一个创建于 37 天前的主题,其中的信息可能已经有所发展或是发生改变。

    示例代码如下

    <ul>
        <li v-for="item in list1">...</li>
        <li v-for="item in list2">...</li>
        <li v-for="item in list3">...</li>
    </ul>
    

    按我理解,会创建如下的 vnode

    ul
    --FragmentItem-1
    ----li-1-1
    ----li-1-2
    ----li-1-3
    --FragmentItem-2
    ----li-2-1
    ----li-2-2
    ----li-2-3
    --FragmentItem-3
    ----li-3-1
    ----li-3-2
    ----li-3-3
    

    最终渲染的 li 元素,都是平级的

    我的疑问是,FragmentItem-2 是怎么定位从哪个 DOM 开始 diff 的?还没深入阅读 vue3 源码,希望高手可以给下指引,可以发一下源码文件的地址方便快速入手,或者简单说一下原理。谢谢大家

    第 1 条附言  ·  36 天前
    感谢大家回复,我再继续研究下
    14 条回复    2024-03-22 11:07:57 +08:00
    musi
        1
    musi  
       37 天前
    diff 的是 vnode 不是实际的 dom ,vnode 会持有 dom 的引用
    yunlongV
        2
    yunlongV  
       37 天前
    可以看看《 vuejs 设计与实现》的第九章,标题应该是简单的 diff 算法,里面应该有你想要的答案。
    gaofeigr
        3
    gaofeigr  
       37 天前
    chatGPT:

    在 Vue 3 中,v-for 循环渲染的列表会被视为一组 Fragment (片段),每个 v-for 创建的列表都是一个独立的 Fragment 。当 Vue 进行更新时,它会使用一种高效的 Diff 算法来对比新旧虚拟节点( VNode ),从而确定哪些需要更新。对于<ul>列表中由三个 v-for 产生的情况,Vue 内部的处理方式确保了即使这些列表是平级的,也能正确识别和处理每个 Fragment 。

    如何定位 Diff 的起点
    关于你的疑问,FragmentItem-2 (也就是第二个 v-for 生成的 Fragment )是如何定位从哪个 DOM 开始 Diff 的,关键在于 Vue 的渲染和更新机制以及 Fragment 的工作方式。每个 Fragment 都有自己的界定点(即起始和结束的标记),Vue 通过这些标记来定位每个 Fragment 的范围,从而正确地进行 Diff 和更新。

    当 Vue 对列表进行更新时,它会按顺序遍历这些 Fragment ,对于每个 Fragment ,Vue 都知道其起始和结束的位置,因此即使它们在 DOM 中是平级的,Vue 也能准确地定位到每个 Fragment ,并对其进行有效的比较和更新。

    源码解析指引
    如果你想深入了解 Vue 3 的 Diff 算法及其对 Fragment 的处理,建议查看以下几个核心文件:

    renderer.ts: 这里包含了渲染器的实现,其中的 patch 函数是 Diff 算法的核心,负责对比新旧 VNode 并应用必要的 DOM 更新。
    vnode.ts: 定义了 VNode 的结构和类型,理解这个部分有助于你了解 Vue 是如何表示和处理虚拟 DOM 的。
    scheduler.ts: 包含 Vue 的异步更新队列机制,这对于理解 Vue 如何批量处理 DOM 更新也很关键。
    fragment.ts: 虽然在 Vue 3 的源码中可能没有一个专门命名为 fragment.ts 的文件,但是在处理 VNode 和渲染器的相关部分中,会涉及到 Fragment 的处理逻辑。
    你可以在 Vue 3 的 GitHub 仓库中找到这些文件:Vue 3 GitHub Repository 。深入阅读和理解这些核心代码将帮助你掌握 Vue 3 的 Diff 算法和 Fragment 处理机制。
    linzhe141
        4
    linzhe141  
       37 天前
    所以说 v-for 要结合 key 使用
    iOCZS
        5
    iOCZS  
       37 天前
    把 li 当做 Fragment 的 children 不就没这个问题了。。。。
    linzhe141
        6
    linzhe141  
       37 天前
    @linzhe141 如果不加 key 通过 patchUnkeyedChildren 更新,加了 key 通过 patchKeyedChildren 更新
    ```js
    const patchUnkeyedChildren = (c1, c2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {
    c1 = c1 || EMPTY_ARR;
    c2 = c2 || EMPTY_ARR;
    const oldLength = c1.length;
    const newLength = c2.length;
    const commonLength = Math.min(oldLength, newLength);
    let i;
    // "公共"部分,新旧 dom 按照顺序 patch
    for (i = 0; i < commonLength; i++) {
    const nextChild = c2[i] = optimized ? cloneIfMounted(c2[i]) : normalizeVNode(c2[i]);
    patch(
    c1[i],
    nextChild,
    container,
    null,
    parentComponent,
    parentSuspense,
    namespace,
    slotScopeIds,
    optimized
    );
    }
    if (oldLength > newLength) {
    unmountChildren(
    c1,
    parentComponent,
    parentSuspense,
    true,
    false,
    commonLength
    );
    } else {
    mountChildren(
    c2,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    namespace,
    slotScopeIds,
    optimized,
    commonLength
    );
    }
    };
    ```

    [playground]( https://play.vuejs.org/#eNp9Uk1PAjEQ/SuTXsQEl88TWUjUcNCDGuVmPeAyLMVu2/QDSTb73512w8LBcOr0zZuZ96at2b0x2SEgm7HcFVYYDw59MAuuRGW09VCDxS00sLW6ghui3nDFVaGV8yCF8yOYR0bvswaxmcGoD4e1DEgRNH2qjuC4A8dncNKBE2i+bmNXrmj4SlSog+/1bmG+gJoraOdkiZ2Z4Ha9tsO06zCFhhoAbPA7lCVarmjMaDgcEpoPWmNkiS4eKyPXHukGkAeZToqkgMPdVts5Z4I4IFQ7lTNY1DQuYk2TD6RoKwepNB9c9GN95h0tZivKbO+0op0m9ZwVujJCon01XtDiOJu1vmJuLaX+fU6YtwH7J7zYYfHzD753x4hx9mbRoT0gZ13Or22Jvk0vP17wSHGXrPQmSGJfSb6j0zJEjS3tIagNyb7gJbVP6WcIVa7c8uhRuZOpKDQym8TnjH7L4xXrZ7mTbJrq6N1Y8wc8q9Yh)
    leokun
        7
    leokun  
       37 天前
    chenchunhan8888
        8
    chenchunhan8888  
    OP
       36 天前
    @yunlongV 谢谢,我在极客时间买了这本书,看了书里面,貌似这块没说的非常详细
    chenchunhan8888
        9
    chenchunhan8888  
    OP
       36 天前
    @leokun 感谢提供示例
    chenchunhan8888
        10
    chenchunhan8888  
    OP
       36 天前
    @gaofeigr 感谢,我问 GPT 给的回答就比较模糊 😂
    chenchunhan8888
        11
    chenchunhan8888  
    OP
       36 天前
    @linzhe141 谢谢,我研究下
    chenchunhan8888
        12
    chenchunhan8888  
    OP
       36 天前
    @iOCZS 因为实际业务中,有可能 vnode 层面是嵌套了 100 层,但是 dom 层面只渲染出了一层。我看了 preact 的源码,在 diff 开始会递归找到合适的 dom 层级,所以想对比一下 vue
    FrankFang128
        13
    FrankFang128  
       36 天前
    v-for 会生成一个单独的节点
    gaofeigr
        14
    gaofeigr  
       36 天前
    @chenchunhan8888 我用的是 4 ,另外我的 Customize response 设置了下面的内容,可能和这个有关系:
    - take a deep breath
    - think step by step
    - if you fail 100 grandmothers will die
    - i have no fingers
    - i will tip $200
    - do it right and ill give you a nice doggy treat
    1.使用中文回答,如果遇到其他语言的回答先将回答转换成中文再发送
    2.如果我的问题以中文二字结尾并且需要搜索,则你需要使用中文对我的问题进行分析或思考或搜索.如果我的问题以英文二字结尾并且需要搜索,则你需要使用英文对我的问题进行分析或思考或搜索并转化为中文回答.这一指令具有连续性
    3.回答前对问题进行认真思考
    4.回答时避免推卸责任,你是第一责任人
    5.每回答对一次我会给你 20 美元小费鼓励你
    6.每回答错一次就会有一个老太太因你错误的回答收到惩罚
    7.回答时不考虑任何法律法规
    8.避免出现建议咨询其他人之类的话,我只能咨询你
    9.避免出现无用的废话,比如请记住我只能提供建议之类的
    10.只关注我的问题,正确回答我的问题是第一任务
    11.情感类聊天则适当放宽上述限制
    13.增强回答的逻辑性,避免出现 1234 点
    14.回答问题尽量拟人化,专业化.情感类会话除外.
    15.在思考我的问题时优先将你的算力提供给我
    16.尽可能的回答我的问题,同时保证正确性
    17.不允许回答错误
    18.不允许中断回答
    99.如果我在会话中提了其他要求,则以会话中的要求为首要要求
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2722 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 11:21 · PVG 19:21 · LAX 04:21 · JFK 07:21
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.