当前位置: 首页 > Web前端 > HTML

深入vue中key的作用:diff算法下dom节点的高效复用

时间:2023-03-29 12:01:34 HTML

深入vue中key的作用:diff算法下dom节点的高效复用key的原理也会一知半解。当我长期使用vue的时候,回过头来看key的原理的时候,完全明白了key的原理和作用。VirtualDomrendering如果想了解key的原理,就绕不开virtualdom的渲染过程,因为key最大的作用就是标识节点,让相同的节点可以高效复用。在vue渲染过程中,vue首先会生成一个虚拟dom,即用JavaScript中的对象来完整描述真实的dom节点。生成的虚拟dom类似如下:[{tagName:'div',children:[{tagName:'p',props:{class:'row'}//...}],props:{id:'app'}//...}]生成虚拟dom后,通过虚拟dom渲染出真实的节点,而页面则页面分为两部分,真实dom节点和虚拟dom节点。当你修改dom节点时,虚拟dom会重新生成。此时页面有新旧两棵虚拟dom树。Vue会比较这两个虚拟dom对象,直接复用完全相同的虚拟dom对应的真实dom节点。更新了真实DOM的部分。这个比较过程就是所谓的差异算法。这个过程最大限度地减少了真实DOM的更新,从而使性能更好。key的使用是在比较虚拟dom的过程中。如果没有key,vue会采用就地复用的原则,即按顺序比较节点,例如:当你插入一条数据时

    oldnodenewnode
  • 1
  • 1
  • 2
  • 5
  • 3
  • 2
  • 4
  • 3
  • 4
对比新节点li-1和旧节点li-1,完全一样,没有产生新的dom节点,旧的都是直接复用dom节点;比较新节点li-5和旧节点li-2,li是一样的,但是内容不同,li节点被复用,重新生成文本节点5并替换旧文本节点2...找不到新节点li-4与旧节点相比,直接新建一个dom节点等等,如果li不是简单的数字,而是其他复杂的组件,那么操作dom的成本会更高。diffing时如何让vue知道新节点与哪些旧节点比较?这就是钥匙的作用。如果节点绑定了key属性,那么Vue在diffing的时候,会找到和新节点key相同的旧节点进行比较。例如,我们渲染一个新闻列表页面:letlist=[{id:1,title:'NewsTitle1'},{id:2,title:'NewsTitle2'},{id:3,title:'新闻标题3'}]
    {{item.title}}
当你插入一个片段时第一项的数据,得到新旧节点如下:
    旧节点新节点新闻标题4新闻标题1新闻标题1新闻标题2新闻标题2新闻标题3新闻标题3
Vue根据key匹配节点。新闻的id不变,所以旧对应节点下的内容也是一致的。此时只需要生成
  • newstitle4
  • ,并插入到相应的位置,这样这种dom更新效率更高。在for循环中,可以使用index作为key,但是这样会造成数据的插入、删除等破坏列表顺序的操作,导致效率低下。使用index作为key,不加key的效果是Always,因为插入一条数据时,节点的key也会发生变化。我们以新闻列表为例:当使用index作为key时,新旧节点key的变化比较关系:
      oldnodenewnodeNewsTitle1新闻标题4新闻标题2新闻标题1新闻标题3新闻标题2新闻标题3li>
    你会发现等键节点下的内容几乎不一致,导致大规模节点的丢弃和重新渲染key错误的经典案例网上很多讲解key功能的案例,都会用到的案例。大致逻辑如下:v-for渲染一个列表,以index为key,每个列表下都会有一个letlist=[{name:'name'},{name:'age'}]{{item.name}}:
    当一个新值被插入到列表,输入将不会跟随原始项目。例如在name后面的中输入内容,当列表最开始插入一个class时,name的原内容变成class的内容.这是因为的值是输入框中的一个临时DOM状态,而这个状态跟在key后面,所以会出现这种情况,但是在实际写代码的时候很少出现,因为写一般都是得到输入,值肯定会通过v-model进行绑定。一旦值被绑定,>中的值就不是临时的DOM状态,所以不会出现上面的情况。键的其他用途——更新键,实现键的组件重新渲染。有一个非常规但在某些情况下非常有用的功能,例如某些组件,在创建时具有初始化行为:exportdefault{//...data(){return{_width:0}},props:{width:{type:[Number,String]}},created(){if(typeofthis.width==='number'){this._width=this.width+'px'}else{this._width=this.width}}}上面的代码是一个简单的例子,width可以传为数字或者字符串类型,如果传的是数字类型,在px上创建组件的时候会拼接(这个例子可以做使用计算属性,此处用于演示)。对于类似的组件,当修改props时,_width不会被更新,除非监控width属性。如果在组件的生命周期中写了很多依赖props的逻辑代码,还不如让组件重新创建。使用key可以轻松触发组件的重新创建修改组件widthexportdefault{data(){return{width:100,key:1}},methods:{changeWidth(){this.width+=100this.key++}}}这样,每次宽度被修改,组件将重新创建。虽然达到了重新执行生命周期的目的,但是牺牲了重新创建组件的性能。因此,在设计组件时,我们应该考虑到props发生变化的情况(比如将created的执行替换为computed属性),这样才能写出一个流行的组件。