vue和算法
㈠ Vue2.x和Vue3.x渲染器的diff算法
简单来说,diff算法有以下过程
正常Diff两个树的时间复杂度是 O(n^3) ,但实际情况下我们很少会进行 跨层级的移动DOM ,所以Vue将Diff进行了优化,从 O(n^3) -> O(n) ,只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。
Vue2的核心Diff算法采用了 双端比较 的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。
Vue3.x借鉴了 ivi 算法和 inferno 算法
在创建VNode时就确定其类型,以及在 mount/patch 的过程中采用 位运算 来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能困棚上较Vue2.x有了提升。(实悄宴际的实现可以结合Vue3.x源码看。)
该算法中还运用了 动态规划 的思想求解最长递归子启尺银序列。
㈡ vue和react的diff算法比较
相同点:
Vue和react的diff算法,都是不进行跨层级比较,只做同级比较。
不同点:
1.Vue进行diff时,调用patch打补丁函数,一边比较一边给真实的DOM打补丁
2.Vue对比节点,当节点漏粗元素类型相同,但是className不同时,认为是不同类型的元素,删除重新创建,而react则认为是同类型节点,进行修改操作
3.① Vue的列表比对,采用从两端到中间的方式,旧集合和新集合两端各历液存在两个指针,两两进行比较,如果匹配上了就按照新集合去调整旧集合,每次对比结束后,指针向队列中间移动;
②而react则是从左往右依次对比,利用元素的index和标识lastIndex进行比较,如果满足index < lastIndex就移动元返烂镇素,删除和添加则各自按照规则调整;
③当一个集合把最后一个节点移动到最前面,react会把前面的节点依次向后移动,而Vue只会把最后一个节点放在最前面,这样的操作来看,Vue的diff性能是高于react的
㈢ 彻底理解vue的patch流程和diff算法
上一篇 《vue异步更新流程梳理》 梳理了数据从赋值到更新到视图的整体流程。但是最后的步骤 vm._update(vm._render()) 只是粗略的提了一嘴,现在就仔细的研究它内部的细节,搞清楚patch流程和diff原理是我们看源码的重中之重。
当更新数据的时候就会执行这个updateComponent方法,即方法里面的 vm._update(vm._render()) ,vm.render() 得到一个vnode,那么vm._update到底干什么? 进去看看
至此,无论是初始化还是更新都是靠patch来完成的 ,我们只需要看update流程就可以了。进入patch内部
patch函数主要接收oldVnode 与 vnode两个参数,其实就是新旧两棵虚拟树。这里经过判断条件 !isRealElement && sameVnode(oldVnode, vnode),不是真实节点 且是相同的vnode,进入patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly); 我们只要关注oldVnode, vnode这两个参数即可。
按照我们的例子,此时的oldVnode 与 vnode分别是
此处只列出关键属性tag, key, elm, children,elm,还有很多其他的属性没有列出。真实的虚拟树节点应该是如下图
我们能看出 两个vnode之间就是children[0]的不同:
追踪流程发现,我们进入oldVnode 与 vnode的children进行对比,在updateChildren函数昌迹中。
我们先不去看updateChildren的逻辑,继续看patchVnode这个函数其他的逻辑分支,得出 oldVnode 与 vnode的对比流程 :
总结: patchVnode这个方法的主要作用是悉纤对比两个虚拟节点过程中去更新真实dom
接下来我们进入updateChildren流程,这是两个children的对比,看一下这个函数的定义
函数解读:
下面是两个数组进行diff的流程,也就是diff算法
diff解读:
新旧耐陆并两个数组,都有双端指针,两端指针向中间靠拢,直到某个数组的两端指针相交则退出循环。
在这个过程中,会先判断是否有以下四种情况
如果不符合这4种情况,那就基于旧数组遍历一次,拿到每个节点的key和index,就是oldKeyToIdx: {key1: 0, key2: 1}这种情况。然后去新数组首个节点开始匹配,匹配到就进行递归patchVnode流程,没匹配到就进行创建新节点,插入到真实dom节点里面去。
当循环结束,此时要么是旧数组相交,要么是新数组相交,只有这两种情况:
至此diff流程结束。
两个虚拟树进行对比:
patch(oldVnode, vnode) -> patchVnode(oldVnode, vnode) -> updataChildren(oldCh, newCh)
在updataChildren(oldCh, newCh)的过程中也会进行 patchVnode(oldVnode, vnode) ,如此虚拟树深度优先递归diff完成。
更加详细直观的图看此链接
https://www.processon.com/view/5e809004e4b08e4e2447d02e
㈣ 简单几句话,知道什么是回流重绘、vue虚拟dom、diff算法和key
1.什么是vue虚拟dom。先知道什么是dom树。
众所周知,一个页面形成的流程。(顺便聊一下回流和重绘)
(1) 解析 HTML===>生成DOM树
(2) 解析CSS===>生成CSSDOM树
(3) Render Tree ===> 从Dom树的根节点开始遍历每个可见的节点(因为这里面有display:none、scrpi等的,不被遍历。)
对于每个可见的节点,找到其对应的CSSDOM规则,并且应用。
生成Render Tree 。
fine,问题来了,什么是回流呢?就是在生成render Tree 的时候,有的CSS涉及到了HTML的尺寸(width/height)、布局改变、隐藏等。详细的可以去搜一下怎么会造成回流。<h3>所有的页面都至少会有一次回流,因为第一次生成render tree一定会回流</h3>
Render Tree生成后,layout(布局)就完成了开始绘制(添加属性,类似于颜色啊,大小啊之类的不会影响布局的属性)。如果说不出意外你不去改的话这辈子和个Render Tree就永远是这个树了。HTML页面就渲染结束了。
但是如果你想改。好嘛,我们就要开始判断你改的是什么了。如果说是影响布局的,那就是回流===>重绘
如果说你只想改个颜色啥的那就直接是重绘,没有回流。
<h3>杰伦:回流必然会带来重绘,但是重绘不一定会回流</h3>
关于如何优化,可以搜索回流和重绘
参考图:
好了,简单的理解了一些html解析和dom树的生成毕配流程。就可以解释什么是虚拟dom了。
虚拟dom也还是那个dom。那为啥不用真实的dom呢?因为真实的DOM你一操作,它立马给你回流重手孝指绘,可能你有10个事件在等着干,一个个都要回流重绘一遍太影响性能了。
(高光打过来!)虚拟dom就站出来了。
虚拟dom:如果有10次更新dom的动作,虚拟dom不会立即操作dom,而是将这10次更新的内容储存起来,通过diff算法,把新的dom(vue刚构造的虚拟dom)和旧的dom(可能是页面上现在显示的真实的dom)进行对比。然后渲染对比完的DOM。
问题来了:什么是diff呢?这是一个算法,有兴趣的可以自己搜一下详细了解诶。我这里只是简单的介绍说这个东西就是 :头头对比。肚子肚子对比。脚脚对比。同级对比,不会跨级对比。就是我的新头和我的旧头对比。我的新肚子和我的旧肚子对比。对比完了去页面上生成一个新的我。
上面提到了patch阶段,顺便说一下key diff算法会通过key可以判断这两个虚拟dom是不是同一个dom,所以我们key尽量都要写慎前上,并且尽量不要使用索引作为key。可以使用 'xx-index' 方式写key。方便你我它~~
好了。本文over 以上都是自己复习的时候总结的内容,如果有问题请留言 Thanks♪(・ω・)ノ
㈤ web前端diff 算法深入一下
有同学问:能否详细说一下 diff 算法。
详细的说,请阅读这篇文章,有疑问的地方欢迎留言一起讨论。
因为 diff 算法是 vue2.x , vue3.x 以及 react 中关键核心点,理解 diff 算法,更有助于理解各个框架本质。
说到“diff 算法”,不得不说“虚拟 Dom”,因为这两个息息相关。
比如:
等等
我们先来说说虚拟 Dom,就是通过 JS 模拟实现 DOM ,接下来难点就是如何判断旧对象和新对象之间的差异。
Dom 是多叉树结构,如果需要完整的对比两棵树的差异,那么算法的时间复杂度 O(n ^ 3),这个复杂度很难让人接收,尤其在 n 很大的情况下,于是 React 团队优化了算法,实现了 O(n) 的复杂度来对比差异。
实现 O(n) 复杂度的关键就是只对比同层的节点,而不是跨层对比,这也是考虑到在实际业务中很少会去跨层的移动 DOM 元素。
虚拟 DOM 差异算法的步骤分为 2 步:
实际 diff 算法比较中,节点比较主要有 5 种规则的比较
部分源码 https://github.com/vuejs/vue/blob//src/core/vdom/patch.js#L501 如下:
在 reconcileChildren 函数的入参中
diff 的两个主体是:oldFiber(current.child)和 newChildren(nextChildren,新的 ReactElement),它们是两个不一样的数据结构。
部分源码
很多时候手工优化 dom 确实会比 virtual dom 效率高,对于比较简单的 dom 结构用手工优化没有问题,但当页面结构很庞大,结构很复杂时,手工优化会花去大量时间,而且可维护性也不高,不能保证每个人都有手工优化的能力。至此,virtual dom 的解决方案应运而生。
virtual dom 是“解决过多的操作 dom 影响性能”的一种解决方案。
virtual dom 很多时候都不是最优的操作,但它具有普适性,在效率、可维护性之间达到平衡。
virutal dom 的意义:
vue2.x 的 diff 位于 patch.js 文件中,该算法来源于 snabbdom,复杂度为 O(n)。了解 diff 过程可以让我们更高效的使用框架。react 的 diff 其实和 vue 的 diff 大同小异。
最大特点:比较只会在同层级进行, 不会跨层级比较。
对比之前和之后:可能期望将 直接移动到
的后边,这是最优的操作。
但是实际的 diff 操作是:
vue 中也使用 diff 算法,有必要了解一下 Vue 是如何工作的。通过这个问题,我们可以很好的掌握,diff 算法在整个编译过程中,哪个环节,做了哪些操作,然后使用 diff 算法后输出什么?
解释:
mount 函数主要是获取 template,然后进入 compileToFunctions 函数。
compileToFunction 函数主要是将 template 编译成 render 函数。首先读取缓存,没有缓存就调用 compile 方法拿到 render 函数的字符串形式,在通过 new Function 的方式生成 render 函数。
compile 函数将 template 编译成 render 函数的字符串形式。后面我们主要讲解 render
完成 render 方法生成后,会进入到 mount 进行 DOM 更新。该方法核心逻辑如下:
上面提到的 compile 就是将 template 编译成 render 函数的字符串形式。核心代码如下:
compile 这个函数主要有三个步骤组成:
分别输出一个包含
parse 函数:主要功能是 将 template 字符串解析成 AST(抽象语法树) 。前面定义的 ASTElement 的数据结构,parse 函数就是将 template 里的结构(指令,属性,标签) 转换为 AST 形式存进 ASTElement 中,最后解析生成 AST。
optimize 函数(src/compiler/optomizer.js):主要功能是 标记静态节点 。后面 patch 过程中对比新旧 VNode 树形结构做优化。被标记为 static 的节点在后面的 diff 算法中会被直接忽略,不做详细比较。
generate 函数(src/compiler/codegen/index.js):主要功能 根据 AST 结构拼接生成 render 函数的字符串 。
其中 genElement 函数(src/compiler/codgen/index.js)是根据 AST 的属性调用不同的方法生成字符串返回。
总之:
就是 compile 函数中三个核心步骤介绍,
patch 函数 就是新旧 VNode 对比的 diff 函数,主要是为了优化 dom,通过算法使操作 dom 的行为降低到最低, diff 算法来源于 snabbdom,是 VDOM 思想的核心。snabbdom 的算法是为了 DOM 操作跨级增删节点较少的这一目标进行优化, 它只会在同层级进行,不会跨层级比较。
总的来说:
在创建 VNode 就确定类型,以及在 mount/patch 的过程中采用位运算来判断一个 VNode 的类型,在这个优化的基础上再配合 Diff 算法,性能得到提升。
可以看一下 vue3.x 的源码:https://github.com/vuejs/vue/blob//src/core/vdom/patch.js
对 oldFiber 和新的 ReactElement 节点的比对,将会生成新的 fiber 节点,同时标记上 effectTag,这些 fiber 会被连到 workInProgress 树中,作为新的 WIP 节点。树的结构因此被一点点地确定,而新的 workInProgress 节点也基本定型。在 diff 过后,workInProgress 节点的 beginWork 节点就完成了,接下来会进入 completeWork 阶段。
snabbdom 算法:https://github.com/snabbdom/snabbdom
定位:一个专注于简单性、模块化、强大功能和性能的虚拟 DOM 库。
snabbdom 中定义 Vnode 的类型(https://github.com/snabbdom/snabbdom/blob//src/vnode.ts#L12)
init 函数的地址:
https://github.com/snabbdom/snabbdom/blob//src/init.ts#L63
init() 函数接收一个模块数组 moles 和可选的 domApi 对象作为参数,返回一个函数,即 patch() 函数。
domApi 对象的接口包含了很多 DOM 操作的方法。
源码:
https://github.com/snabbdom/snabbdom/blob//src/init.ts#L367
源码:
https://github.com/snabbdom/snabbdom/blob//src/h.ts#L33
h() 函数接收多种参数,其中必须有一个 sel 参数,作用是将节点内容挂载到该容器中,并返回一个新 VNode。
在 vue2.x 不是完全 snabbdom 算法,而是基于 vue 的场景进行了一些修改和优化,主要体现在判断 key 和 diff 部分。
1、在 snabbdom 中 通过 key 和 sel 就判断是否为同一节点,那么在 vue 中,增加了一些判断 在满足 key 相等的同时会判断,tag 名称是否一致,是否为注释节点,是否为异步节点,或者为 input 时候类型是否相同等。
https://github.com/vuejs/vue/blob//src/core/vdom/patch.js#L35
2、diff 差异,patchVnode 是对比模版变化的函数,可能会用到 diff 也可能直接更新。
https://github.com/vuejs/vue/blob//src/core/vdom/patch.js#L404
㈥ 你怎么理解vue中的diff算法
diff算法是虚拟 DOM 的必然产物,通过新旧虚拟 DOM 对比,将变化的地方更新在真实 DOM 上,另外也需要 diff 高效的执行对比歼桐过程,从而降低时间复杂度
vue2中为了降低 watcher 力度,每个组件只有芹烂一个 watcher 与之对应,只有引入 diff 才能精确的找到发生变化的地方
diff 之所以发生变化,是引入组件数据的变化调用了 set 方法,导致 watcher能够监控到数据的变化,导致其触发 updateComponent 中 pathVnode方法,在 pathVnode 函数中对对比上一次渲染结果和新渲染结果.两个节比较的过程就是发生diff的过程.整个更新过程叫做 path 过程,也叫打补丁的过程
diff 过程整体遵从深度优先,同层比较的策略;两个节点之间比较会根据它们是否拥有子节点或者文本节点做不同的操作.比较两组节点是算法的重点.首先假设头尾节点氏首坦相同做 4 次比较尝试,如果这 4 中方式都没有找到,就按照通用方式遍历查找.查找结束在按照情况处理剩下的节点;借助 key,通常可以精确的找到相同的节点,因此整个 path 过程是非常高效的
㈦ 面试中的网红Vue源码解析之虚拟DOM,你知多少呢深入解读diff算法
众所周知,在前端的面试中,面试官非常爱考dom和diff算法。比如,可能会出现在以下场景
滴滴滴,面试官发来一个面试邀请。接受邀请📞
我们都知道, key 的作用在前端的面试是一道很普遍的题目,但是呢,很多时候我们都只浮于知识的表面,而没有去深挖其原理所在,这个时候我们的竞争力就在这被拉下了。所以呢,深入学习原理对于提升自身的核心竞争力是一个必不可少的过程。
在接下来的这篇文章中,我们将讲解面试中很爱考的虚拟DOM以及其背后的diff算法。 请认真阅读本文~文末有学习资源免费共享!!!
虚拟DOM是用JavaScript对象描述DOM的层次结构。DOM中的一切属性都在虚拟DOM中有对应的属性。本质上是JS 和 DOM 之间的一个映射缓存。
要点:虚拟 DOM 是 JS 对象;虚拟 DOM 是对真实 DOM 的描述。
diff发生在虚拟DOM上。diff算法是在新虚拟DOM和老虚拟DOM进行diff(精细化比对),实现最小量更新,最后反映到真正的DOM上。
我们前面知道diff算法发生在虚拟DOM上,而虚拟DOM是如何实现的呢?实际上虚拟DOM是有一个个虚拟节点组成。
h函数用来产生虚拟节点(vnode)。虚拟节点有如下的属性:
1)sel: 标签类型,例如 p、div;
2)data: 标签上的数据,例如 style、class、data-*;
3)children :子节点;
4) text: 文本内容;
5)elm:虚拟节点绑定的真实 DOM 节点;
通过h函数的嵌套,从而得到虚拟DOM树。
我们编写了一个低配版的h函数,必须传入3个参数,重载较弱。
形态1:h('div', {}, '文字')
形态2:h('div', {}, [])
形态3:h('div', {}, h())
首先定义vnode节点,实际上就是把传入的参数合成对象返回。
[图片上传失败...(image-7a9966-1624019394657)]
然后编写h函数,根据第三个参数的不同进行不同的响应。
当我们进行比较的过程中,我们采用的4种命中查找策略:
1)新前与旧前:命中则指针同时往后移动。
2)新后与旧后:命中则指针同时往前移动。
3)新后与旧前:命中则涉及节点移动,那么新后指向的节点,移到 旧后之后 。
4)新前与旧后:命中则涉及节点移动,那么新前指向的节点,移到 旧前之前 。
命中上述4种一种就不在命中判断了,如果没有命中,就需要循环来寻找,移动到旧前之前。直到while(新前<=新后&&旧前<=就后)不成立则完成。
如果是新节点先循环完毕,如果老节点中还有剩余节点(旧前和旧后指针中间的节点),说明他们是要被删除的节点。
如果是旧节点先循环完毕,说明新节点中有要插入的节点。
1.什么是Virtual DOM 和Snabbdom
2.手写底层源码h函数
3.感受Vue核心算法之diff算法
4.snabbdom之核心h函数的工作原理
1、零基础入门或者有一定基础的同学、大中院校学生
2、在职从事相关工作1-2年以及打算转行前端的朋友
3、对前端开发有兴趣人群
㈧ 为什么学习Vue框架
vue框架算是最近前端开发很好的工具。可以突破以前所没有实时更新页面。很有发展前景,很多大公司现在正在使用。
Vue框架诞生于2014年,其作者为中国人——尤雨溪,也是新人最容易入手的框架之一,不同于React和Angular,其中文文档也便于大家阅读和学习。Vue用于构建交互式的Web界面的库,是一个构建数据驱动的Web界面渐进式框架,该框架遵循CMD规范,并且提供的设计模式为MVVM模式(Model->View->View-Model)和一个可组合的组合型组件系统,具有简单的、灵活的API(接口)。该框架继承了React的虚拟DOM技术和Angular的双向数据绑定技术,是一款较新的功能性框架。
在这里介绍下什么是虚拟DOM和双向数据绑定:
1、虚拟DOM(Virtual DOM),顾名思义,从字面上理解就是虚构的DOM树,当我们用传统的原生API或者jQuery去操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程。即使计算机硬件一直在更新迭代,但是操作真实DOM的代价仍旧很昂贵,真实的DOM节点,哪怕是一个最简单的div也包含很多属性,所以频繁的操作,会导致页面卡顿,影响用户的体验。为了解决这个浏览器性能问题,虚拟DOM(Virtual DOM)就被设计出来了,其核心算法是Diff算法。它会将一次操作过程中对真实DOM所有更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性attach到DOM树上,通知浏览器去执行绘制工作,避免了大量的无谓的计算量。
用js对象模拟DOM节点的好处是:页面的更新可以先全部反应在js对象上,操作内存中的js对象的速度明显要快的多。等更新完成后,在将最终的js对象映射成真实的DOM,交由浏览器去绘制。(提高了性能,并且运行速度快)
2、双向数据绑定,在讲双向数据绑定前,我们要想说下单向数据绑定,单向数据绑定,就是把Model绑定到View上,当我们用JavaScript代码更新Model时,View就会自动更新了(Model-->View)。那么双向数据绑定就是,用户更新了View,Model的数据也会自动被更新(Model<-->View)。什么情况下用户可以更新View呢?举个最直接的例子,填写表单,当用户填写表单时,View的状态就被更新了,如果此时MVVM框架可以自动更新Model的状态,那么就相当于我们把Model和View做了双向数据绑定。其原理是我们要对input进行value 的属性绑定(v-bind:value=”...”)将Model中的变量绑定到View上(Model->View)以及当用户对input进行操作时,进行事件监听(v-on:input=”...”)将View上的更新传回Model中(View->Model)从而实现双向数据绑定,在Vue中,以上操作过于繁琐,便提供了v-model直接实现双向数据绑定的效果。
在进行Vue项目开发过程中,我们可以通过script标签引入式写法来引入vue或者是nodejs自带的包管理工具npm安装vue。并且通过new Vue()进行新建一个Vue的实例对象,其下有很多属性,包括el、data、methods、computed、watch等等,el为指向页面的节点元素,data存储数据,数据类型包括simple datatype(简单数据类型)以及complex datatype(复杂数据类型),用插值表达式{{}}显示,在插值显示的时候,不需要写上data,methods内存储方法,通过fn()的形式调用方法,computed内存储也是方法,但是其为计算数据,复杂逻辑的应该存储在computed中,计算属性是基于它们的依赖进行缓存的,由于computed带有一层缓存,所以只有在它的相关依赖发生改变时才会重新运行,而methods则是调用一次生成一次,computed中的方法调用时不需要加()的,watch为监听,监控,监听data中的属性值也可以监控对象,存在两个参数(currentValue当前值和prevValue之前值)。
㈨ vue-diff算法
渲染真实DOM的开销是很大的,轻微的操作都可能导致页面重新排版,非常耗性能。 相对于DOM对象,js对象处理起来更快,而且更简单。 通过diff算法对比新旧vdom之间的差异,可以批量的、最小化的执行 dom操作,从而提高性能。
常规:O(n^3) 遍历树1; 遍历树2; 排序; 1000个节点,十亿的量级。
vue diff:O(n) 只比较同一层级 ;tag不相同,直接删掉重建; 通过key来标识区分相同节点。
旧节点:A、B、C、D
新节点:A、E、B、C、D
1.1 两个节点key是否相同(两个key都没有,即 undefined === undefined)
1.2 两个节点tag标签名是否一样
1.3 两个节点是否都为注释节点
1.4 两个节点的data isDef是否都相等(isDef:data !== undefined && data !== null)
1.5 两个节点的input类型是否相同
1.6 节点a是否为异步占位
1.7 两个节点的异步函数是否相等
1.8节点b异步函数的error是否为空(isUndef:data === undefined && data === null)
2.1 新旧节点都有子节点,调用updateChildren重春握排子节点
2.2 只有新节点有子节点,调用addVnodes添加子节点
2.3 只有旧节点有子节点,调用removeVnodes移除子节点
2.4 如果是文本节点,调用setTextContent更新节点文本内容
3.1.1 旧头不存在,将旧头游标往后移一位
3.1.2 旧尾不存在,将旧尾游标往前移一位
3.1.3 旧头、新头相同,更新节点,并将头部游标往后移一位
3.1.4 旧尾、新尾相同,更新节点,并将尾部游标往前移一位
3.1.5 旧头、洞烂新尾相同,更新节点,并且将旧头移到尾部,旧头游标往纳森漏后移一位,新尾游标往前移一位
3.1.6 旧尾、新头相同,更新节点,并且将旧尾移到头部,新头游标往后移一位,旧尾游标往前移一位
3.1.7 拿新头遍历旧子节点,找不到则新建一个节点;找到判断节点是否相同,相同则更新节点,移动老节点,不同则新建一个节点
3.2.1 旧子节点先遍历完毕,说明有新增节点,批量增加
3.2.2 新子节点先遍历完毕,说明有节点删除,批量移除
㈩ Vue中使用Sortable
之前开发一个后台管理系统,里面用到了 Vue 和 Element-UI 这个组件库,遇到一个挺有意思的问题,和大家分享一下。
场景是这样,在一个列表展示烂配亮页上,我使用了 Element-UI 的表格组件,新的需求是在原表格的基础上卖旅支持拖拽排序。但是原有的组件本身不支持拖拽排序,而且由于是直接引入的 Element-UI ,不方便修改它的源码,所以比较可行的方法只能是 直接操作DOM 。
具体的做法是在 mounted 生命周期函数里,对 this.$el 进行真实DOM的操作,监听 drag 的一系列事件,在事件回调里移动DOM,并更新data。
HTML5 Drag 事件还是挺多的,和 Touch 事件差不多,自己手工实现也可以,不过这里就偷了个懒,直接用了一个开源的 Sortable 库,直接传入 this.$el ,监听封装后的回调,并且根据Vue的开发模式,在移动DOM的回调里更新实际的Data数据, 保持数据和DOM的一致性 。
如果你以为到这就结束了,那就大错特错,偷过的懒迟早要还。。。本以为这个方案是很美好的,没想到刚想调试一下,就出现了诡异的现象:A和B拖拽交换位置之后,B和A又神奇得换回去了!这是怎么回事?似乎我们的操作没有什么问题,在真实DOM移动了之后,我们也移动了相应的 data ,数据数组的顺序和渲染出DOM的顺序应该是一致的。
问题出在哪里?我们回忆一下Vue的实现原理,在Vue2.0之前是通过 defineProperty 依赖注入和跟踪的方式实现双向绑定。针对v-for数组指令,如果指定了唯一的Key,则会通过高效的Diff算法计算出数组内元素的差异,进行最少的移动或删除操作。而Vue2.0之后在引入了 Virtual Dom 之后, Children 元素的 Dom Diff 算法和前者其实是相似的,唯一的区别就是,2.0之前Diff直接针对 v-for 指令的数组对象,2.0之后则针对 Virtual Dom 。DOM Diff算法在这里不再赘述,这里解释的比较清楚 virtual-dom diff算法
假设我们的列表元素数组是
渲染出来后的DOM节点是
那么Virtual Dom对应的结构就是
假设拖拽排序之后,真实的DOM变为
此时我们只操作了真实DOM,改编了它的位置,而Virtual Dom的结构并没有改变,依然是
此时我们把列表元素也按照真实DOM排序后变成
这时候根据Diff算法,计算出的Patch为, VNode前两项是同类型的节点 ,所以直接更新,即把$A节点饥宽更新成$B,把$B节点更新成$A,真实DOM又变回了
所以就出现了拖拽之后又被Patch算法更新了一次的问题,操作路径可以简单理解为
根本原因是 Virtual DOM 和 真实DOM 之间出现了不一致。
所以在Vue2.0以前,因为没有引入 Virtual DOM ,这个问题是不存在的。
在使用Vue框架的时候要尽量避免直接操作DOM
3.暴力解决!不走patch更新,通过v-if设置,直接重新渲染一遍。当然不建议这么做,只是提供这种思路~
所以,我们平时在使用框架的时候,也要去了解框架的实现原理的,否则遇到一些棘手的情况就会无从下手~