通过几个问题深入分析Vue中的diff原理
遇到的问题
在使用Vue渲染“可删减”的列表时,错误的使用index作为key,导致列表视图出现错乱。
点击查看问题
- 复现步骤:右侧有两行,在第一行的Input里输入1,在第二行Input里输入2,然后点第一行的“ד删除第一行
- 期待结果:删除第一行后,应该变成“请输入 dog 的个数:2”
- 实际结果:删除第一行后,变成了“请输入 dog 的个数:1”
为什么cat变成了dog,但是<input />
里的1没有变成2呢?
这个问题一下子很难解释,下面我们通过几个小问题,一步一步来分析。
如果我们使用正确的值做为key,那么这个问题其实根本就没有意义。但是,如果我们参透了其中的出错原因,这将给我们带来极大的提升。
为什么会触发组件update
查看使用index作为key例子
- 测试1:打开浏览器控制台,然后删除第一行,查看日志,思考为什么。
- 测试2:先重置页面,然后删除最后一行,查看日志,思考为什么。
测试1的结果
你会发现,删除第一行后,watch
和updated钩子
都执行了,这个结果其实给了我们第一个提示:
删除第一行这句话本质上其实是:删除vue实例数据中list的第一项,并不是删除dom的第一个节点!
对于在dom中的这三个节点,其实是做了如下的变化:
VDOM的diff算法
之所以会这种方式进行dom更新,这决定于vdom的diff算法,我们通过阅读vue源码中src/core/vdom/patch.js
这个文件来一探究竟。具体的来说,就是其中的updateChildren
方法:
不要被这么多变量吓到,其实主要是三组变量,每组四个:
- 第一组是四个指针,分别指向oldCh和newCh的头和尾
- 第二组是四个vnode,分别是四个指针所指的节点
- 第三组是四个辅助变量(413行),用来移动vnode
在我们的例子里,大概是这样:
继续往下看:
又是一坨代码,但也不要被吓到,你会发现if、else if里的逻辑都是差不多的,仔细读两遍,你就会发现其实大概就是:
空节点跳过处理
指针1对应的vnode跟指针3对应的node比,如果是same的就patchVnode进行更新,如果不是same的就往下走
指针2对应的vnode跟指针4对应的node比,如果是same的就patchVnode进行更新,如果不是same的就往下走
指针1对应的vnode跟指针4对应的node比,如果是same的就patchVnode进行更新,如果不是same的就往下走
指针2对应的vnode跟指针3对应的node比,如果是same的就patchVnode进行更新,如果不是same的就往下走
最终,如果到了这一步还不是same的,那就用key最终确认一次
- 先构造一个key to index的map
- 判断当前old vnode的key是否在map里
- 如果不在,就直接createElm
- 如果在,并且是same的,那就patchVnode,然后更新节点内容、顺序
- 如果在,但是不是same的,那就视作新element,执行createElm
当while循环退出时,如果指针1和指针2还没重合,那就代表此时指针1和指针2区域内的vnode是待删除的,所以直接removeVnodes。而如果是指针3和指针4还没重合,那就代表指针3和指针4之间的vnode是待添加的,所以直接addVnodes。至此整个过程结束。
怎么用上面的过程解释“测试1”的结果
再看一下这个图:
按照上面的diff算法,我们会先判断cat和dog是否是same的,其中sameNode方法如下:
也就是说,只要key相同,并且tag、isComment、isDef(data)、sameInputType都是true,那么diff算法就认为是同一个vnode,在这里旧的cat节点和新的dog节点,它们的key都是0,显然符合这个条件。
所以,代码会进入到这里:
此时会使用patchVnode
方法来"patch"旧的这个cat节点,怎么patch呢?
简单地说,就是使用新的props,让这个cat节点进行re-render,re-render的过程中必然也做一些诸如:触发watch,调用updated声明周期钩子之类的事情。 记住这句话,以后会用到!
接下来,dog变成pig,也是同样的道理。
最后,左边oldCh的pig节点哪去了呢?
其实到了这里,while循环就已经退出了,看上一小节的第7步,此时pig节点会直接被remove掉。
关于patchVnode的细节在这里没有写,需要自己去看,关键的地方是在
src/core/vdom/patch.js
的545,552,572行
需要强调一点
可能有人会疑惑,即使我不知道diff算法的细节,在我们删除第一行时,也就是删掉list的第一项时,会触发视图更新,视图更新了,那cat节点肯定就会变成dog,这应该是理所当然的啊。
这里需要强调的是,使用diff算法时,"合适"的原有的节点是会被复用的!cat之所以变成dog,不是因为新建了一个dog节点,而是cat节点被复用,然后使用新的props,通过re-render
实现了视图的更新!
测试2为什么不触发log的打印
到这里,我们就已经解释了:为什么“测试1”会触发watch
和updated
的打印了。
那么为什么测试2不会触发上述打印呢?其实原因很简单,因为patchVnode
提前return
了,没有触发re-render:
回到最开始的问题
如下图,到这里我们应该已经理解为什么删除第一行后,cat会变成dog。但是,为什么<input />
里的1没有变成2呢?
一个简单的解释
我们之前说过:patchVnode
的结果,其实就是使用新的props,让这个cat节点进行re-render。
这里是re-render,它的执行不是unmount一个节点,然后再mount一个新的节点,而是直接使用新的props来receive(更新)一个节点,节点的instance并没有重置,所以re-render的过程中,data压根就没变。
receive这个词出自:React实现原理
一些练手的问题 [可选]
使用空、常量1、index、unique的稳定值、random的随机值来作为key,依次预测视图如何表现、控制台如何打印:
练手问题
这样就结束了吗?
有一个更深层次的问题:这是一个feature还是一个bug?
我又用React写了一个同样的例子:点击查看React版本的问题
你会发现,不管是React还是Vue都会存在这个问题,这肯定不是一个bug,那么这两个框架为什么要这么设计呢?
如果感兴趣,请关注下一篇文章:《思考如何自己写一个React框架》
通过几个问题深入分析Vue中的diff原理相关推荐
- vue指令写在html中的原理,详解Vue中的MVVM原理和实现方法
对Vue中的MVVM原理解析和实现首先你对Vue需要有一定的了解,知道MVVM.这样才能更有助于你顺利的完成下面原理的阅读学习和编写下面由我阿巴阿巴的详细走一遍Vue中MVVM原理的实现,这篇文章大家 ...
- JS每日一题:Vue中的diff算法?
20190125 Vue中的diff算法? 概念: diff算法是一种优化手段,将前后两个模块进行差异对比,修补(更新)差异的过程叫做patch(打补丁) 为什么vue,react这些框架中都会有di ...
- [vue] 你知道vue中key的原理吗?说说你对它的理解
[vue] 你知道vue中key的原理吗?说说你对它的理解 key的作用主要是为了高效的更新虚拟DOM; 如果没有唯一的key, 数据更新时, 相同节点更新前后无法准确一一对应起来,会导致更新效率降低 ...
- [Vue][面试]你怎么理解vue中的diff算法?
你怎么理解vue中的diff算法? #####源码分析1:必要性,lifecycle.js–mountComponent() vue中一个组件一个watcher实例,而组件中可能存在很多个data中的 ...
- Vue中的Diff算法 patch函数-简单Diff算法-双端Diff算法-快速Diff算法-当数据发生改变,视图如何更新?
文章目录 Vue中的Diff算法 概述 前置知识 patch方法 简单Diff算法 总结 双端Diff算法 --vue2 快速Diff算法 --vue3 vue2和vue3 Diff算法的区别 当数据 ...
- 前端面试 vue生命周期钩子是如何实现的?理解vue中模板编译原理?
生命周期钩子在内部会被vue维护成一个数组(vue 内部有一个方法mergeOption)和全局的生命周期合并最终转换成数组,当执行到具体流程时会执行钩子(发布订阅模式),callHook来实现调用. ...
- Vue 中 CSS 动画原理
下面这段代码,是点击按钮实现hello world显示与隐藏 <div id="root"><div v-if="show">hello ...
- vue中的v-model原理,与组件自定义v-model
VUE中的v-model可以实现双向绑定,但是原理是什么呢?往下看看吧 根据官方文档的解释,v-model其实是一个语法糖,它会自动的在元素或者组件上面解析为 :value="" ...
- c# mvvm模式获取当前窗口_对Vue中的MVVM原理解析和实现
首先你对Vue需要有一定的了解,知道MVVM.这样才能更有助于你顺利的完成下面原理的阅读学习和编写 下面由我阿巴阿巴的详细走一遍Vue中MVVM原理的实现,这篇文章大家可以学习到: 1.Vue数据双向 ...
最新文章
- 可见的轮廓线用虚线绘制_为什么你用SketchUp没有别人“快”?
- java正则表达式替换指定字符串_笔记_正则表达式替换字符串中特定范围内的字符。...
- java微妙_10个微妙的Java编码最佳实践
- vim 怎么显示空格_vim 修改tab为四个空格
- error: IO error while decoding xxx.jar with UTF-8
- 如何在Java中同步ArrayList?
- 信息学奥赛一本通 1265:【例9.9】最长公共子序列
- 一些值得注意的算法题——动态规划
- 【Python3_进阶系列_013】Python3-实现文件夹文件的过滤
- Yarn 调度器Scheduler详解
- 学术论文的标准格式是什么?写论文有哪些小技巧?
- 微信开发之微信支付(商户平台提供的方式)
- 英文论文查重率怎么算?
- D4:非成对图像去雾,基于密度与深度分解的自增强方法(CVPR 2022)
- 数据库中“一对一”、“一对多”、“多对多”的判断方法
- 只用70行代码,手把手教你遍历当前windows所有进程!
- 从今天起我想要热爱生活
- 视 频 传 输 技 术
- 公交换乘系统c语言,数据结构课程设计报告(公交换乘).docx
- uva1471 Defense Lines