我第一次接触key,是在学习v-for循环时,不加key标识会报错,对于初学者来说,听过key的原理也会懵懵懂懂,只知其一;不知其二。
当我用了很久的vue,在回头看key的原理时,才彻底弄懂了key的原理和作用。
虚构Dom渲染
要想理解key的原理,绕不开的就是虚构dom的渲染过程,因为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更新,这个比拟的过程就是所谓的diff算法,这样解决使得实在的dom的更新最小化,从而使得性能更优。
key的用途
在比拟虚构dom的过程中,如果没有key,vue会采纳就地复用的准则,也就是按程序来比拟节点,比方:
当你插入一条数据时
<ul> 旧节点 新节点 <li>1</li> <li>1</li> <li>2</li> <li>5</li> <li>3</li> <li>2</li> <li>4</li> <li>3</li> <li>4</li> </ul>
新节点li-1
和旧节点li-1
比拟,完全一致,不生成新dom节点,间接复用旧dom节点;
新节点li-5
和旧节点li-2
比拟,li一样,内容不一样,复用li节点,文本节点5
从新生成并替换旧的文本节点2
...
新节点li-4
找不到旧的节点比照,间接创立新的dom节点
以此类推,如果li里不是简略的数字,而是其余简单的组件,那么操作dom的老本就更高了。
如何让vue在diff时晓得新节点和哪些旧节点去比拟呢?那就是key起的作用,如果给节点绑定了key属性,那vue在diff的时候,会找到与新节点的key雷同的旧节点进行比拟,比方咱们渲染了一页新闻列表:
let list = [ { id: 1, title: '新闻标题1' }, { id: 2, title: '新闻标题2' }, { id: 3, title: '新闻标题3' }]
<ul> <li v-for="item in list" :key="item.id"> {{item.title}} </li></ul>
当你插入一条数据在第一条,失去新旧节点如下:
<ul> 旧节点 新节点 <li key="4">新闻标题4</li> <li key="1">新闻标题1</li> <li key="1">新闻标题1</li> <li key="2">新闻标题2</li> <li key="2">新闻标题2</li> <li key="3">新闻标题3</li> <li key="3">新闻标题3</li> </ul>
vue依据key去匹配节点,新闻的id是不变的,所以找到旧的对应节点下的内容也是统一,此时,只须要生成 <li>新闻标题4</li>
,并在对应的地位插入即可,这样的dom更新效率更高
在for循环中,能够应用index作为key,然而这会导致数据在插入、删除等毁坏列表程序的操作时,造成效率底下,应用index当key和不加key的成果是始终的,因为当插入一条数据时,节点的key也会跟着扭转,还是用新闻列表举例:
当应用index作为key时,新旧节点key的变动和比照关系:
<ul> 旧节点 新节点 <li key="0">新闻标题1</li> <li key="0">新闻标题4</li> <li key="1">新闻标题2</li> <li key="1">新闻标题1</li> <li key="2">新闻标题3</li> <li key="2">新闻标题2</li> <li key="3">新闻标题3</li> </ul>
你会发现,key相等的节点下的内容简直都不统一,这就造成了大规模节点的抛弃和从新渲染。
key谬误的经典案例
在网上很多解说key作用的案例中,都会用到<input />
作用案例,大略逻辑如下:
v-for渲染一个列表,应用index作为key,每个列表下都会有一个<input/>
let list = [ { name: '姓名' }, { name: '年龄' }]
<div v-for="(item,index) in list" :key="index"> {{item.name}}: <input /></div>
当列表被插入一个新值时,input并不会追随原来的item,比方在姓名前面的<input />
输出了内容,当list在第一位插入一个班级时,本来在姓名<input />
的内容变成了班级<input />
的内容了
这是因为<input />
的值在输入框中是长期DOM状态,这个状态追随的是key,所以才会呈现这种状况,然而理论写代码很少会呈现这种状况,因为写<input />
个别是为了获取输出,必定会通过v-model
来绑定值的,一旦绑定了值,<input />
中的值就不是长期DOM状态,那就不会呈现下面那种状况了。
key的其余用处 - 更新key实现组件从新渲染
key有个不太正规,但某些状况十分好用的性能,比方有些组件,在创立的时候有初始化的行为:
export default { // ... data() { return { _width: 0 } }, props: { width: { type: [Number,String] } }, created() { if (typeof this.width === 'number') { this._widht = this.width + 'px' } else { this._widht = this.width } }}
上述的代码是一个简略的列子,宽度能够传数字或字符串类型,如果传的是数字类型,创立组件的时候就拼接上px(该例子能够应用计算属性实现,此处为了展现)。
相似的组件,在props批改的时候,_width并不会被更新,除非监听width属性。
如果组件的生命周期里还写了很多依赖props的逻辑代码,那不如让组件从新创立好了。
利用key,就能够轻易触发组件的从新创立
<my-component :key="key" :width="width"></my-component> <button @click="changeWidth">批改组件宽度</button>
export default { data() { return { width: 100, key: 1 } }, methods: { changeWidth() { this.width += 100 this.key++ } } }
这样,在每次批改width时,组件都会从新被创立。
尽管达到了从新执行生命周期的目标,但就义了从新创立组件的性能,所以咱们在设计组件的时候,应多思考props被改变的状况(比方用计算属性代替created的执行),能力写出一个受欢迎的组件。