就目前所了解的情况,key的作用有以下这些。
- v-for遍历时,用id,uuid之类作为key,唯一标识节点加速虚拟DOM渲染
- 响应式系统没有监听到的数据,用+new Date()生成的时间戳作为key,手动强制触发重新渲染
场景一大同小异司空见惯,场景二是下面这样的:
<div :key="rerender">
<span>Hello Vue.js !</span>
<complexComponent :propObj="propObj" :propArr="propArr" ></complexComponent>
</div>
refresh(){
this.rerender = + new Date();
}
那么vue中key的相关知识点到底是怎样的呢?
- 官方API知识点
- 上面2个使用场景背后的原理是什么?
- 除key外,还有其它强制更新DOM的方法吗?
- 参考资料
官方API知识点
- 在Vue.js中,key是6个特殊属性key, ref, is, slot, slot-scope, scope其中之一。
- key的值可以是number,也可以是string。
- key主要作用于Vue的virtual DOM算法,在diff new nodes list和old nodes list时,作为识别VNode的一个线索。
- 如果不用key,Vue会用一种算法:最小化element的移动,并且会尝试尽最大程度在同适当的地方对相同类型的element,做patch或者reuse。
- 如果使用了key,Vue会根据keys的顺序记录element,曾经拥有了key的element如果不再出现的话,会被直接remove或者destoryed。
- 拥有同一个parent的children必须有unique keys。重复的key的导致render error。
最常用的用法一:v-for
<ul>
<li v-for="item in items" :key="item.id">...</li>
</ul>
最常用的用法二:强制替换element或者component
- 触发组件的lifecycle
- 触发transition
<transition>
<span :key="text">{{ text }}</span>
</transition>
text发生变化时,<span>
会被replaced,而不会patched,因此transition会被触发。
我的理解:
text变化时,span的key发生了变化,也就是说曾经拥有了旧key的span不再出现了,当拥有新值的text作为key时,拥有了新key的span出现了,那么旧key span会被移除,旧transition也会移除,新key span触发渲染,新transition触发。
上面2个使用场景背后的原理是什么?
结合官方API的知识点,现在再来回顾文章开头提出的场景。
场景一:v-for遍历时,用id,uuid之类作为key,唯一标识节点加速虚拟DOM渲染
答案:
- 如果不用key,Vue会用一种算法:最小化element的移动,并且会尝试尽最大程度在同适当的地方对相同类型的element,做patch或者reuse。
- 如果使用了key,Vue会根据keys的顺序记录element,曾经拥有了key的element如果不再出现的话,会被直接remove或者destoryed。
场景二:响应式系统没有监听到的数据,用+new Date()生成的时间戳作为key,手动强制触发重新渲染
<div :key="rerender">
<span>Hello Vue.js !</span>
<complexComponent :propObj="propObj" :propArr="propArr" ></complexComponent>
</div>
refresh(){
this.rerender = + new Date();
}
答案:
- 如果使用了key,Vue会根据keys的顺序记录element,曾经拥有了key的element如果不再出现的话,会被直接remove或者destoryed。
- refresh方法调用后,包含了span和complexComponent的div的key发生了变化,也就是说曾经拥有了旧key的div不再出现了,当拥有新值的rerender作为key时,拥有了新key的div出现了,那么旧key div会被移除,旧span和complexComponent也会移除,新key div触发渲染,新span,带着父组件新propObj和propArr的新complexComponent渲染。
思考:
- 为什么要叫propObj和propArr?
- 带着父组件新propObj和propArr的新complexComponent渲染。 为什么要加粗?
由于Vue.js的obj和arr存在无法检测到数据变化的情况,obj是属性的新增和删除(原因是新增和删除都没有触发setter,watcher未告诉外界更新),arr则是数组内元素重新赋值或者修改length属性(原因是没有使用改变数组本身的方法,没有触发数组原型链拦截器,watcher未告诉外界更新)。
所以!通过赋予新key的方式,移除旧key div,渲染新key div,propObj和propArr在complexComponent组件内会重新触发一次生命周期,做一次重新渲染。此时父组件的propObj和propArr js变量其实已经获取到新值了,只是没有触发DOM也好,VNode也好的重新渲染。需要通过刷新key去force update,说到forceUpdate,可以通过$forceUpdate()去手动强制更新DOM。
除key外,还有其它强制更新DOM的方法吗?
场景:父组件修改传递给子组件的数据,数组数据的更新没有按照this.$set去更新。该怎么办?
this.productImages.forEach((product) => {
if (product.productId in this.productsState) {
product.status = this.productsState[product.productId];
}
});
不使用this.$set去赋值数据的不能rerender的原因是什么?
在Vue.js中,对Array的变化侦测是通过拦截原型的方式实现的。也就通过对push,pop,shift,unshift,splice,sort,reverse,fill,copyWithin去改变数组自身内容的方法做拦截,从而响应。而product.status = this.productsState[product.productId];
没有触发任何改变数组自身的被监听的方法,因此不会rerender。
- 刷新组件的key
- $forceUpdate方法
刷新组件的key
1.这个key加在什么地方比较好?
加在this.productImages的父元素上就好。
若不涉及数据传递,也可以直接加在需要更新的element上。
2.用什么做key值?
现在是粗暴的+new Date()时间戳做key值的。
也可以用双向绑定的值作为key值,保证新旧key值不同就行。
3.key的原理是什么?
vue.js的虚拟DOM算法,在更新vNode时,需要从旧vNode列表中查找与新vNode节点相同的vNode进行更新,如果这个过程设置了属性key,过程就会快很多。
其他具体见上文。
$forceUpdate方法
只能在父组件调用这个方法,手动通知vue实例重新渲染。
// $forceUpdate源码
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
// update源码
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
1.$forceUpdate可以更新的原理分析
product.status = this.productsState[product.productId];
以后,其实此时dep已经发生变化了,但是Vue.js数组响应式的实现由于是拦截原型链方法的方式,没有检测到这个变化,所以不会自动rerender,没有触发update。因此我们通过$forceUpdate的方式,调用包含dep的watcher上的update方法,从而做到rerender。
2.可以在子组件监听事件,父组件发送事件然后只刷新子组件吗?
不可以。
因为dep是父组件的watcher和dep,并不是子组件,是父组件的this.productImages没有被检测到并实时更新,并不是子组件的问题。
参考资料
https://vuejs.org/v2/api/#key
https://vuejs.org/v2/api/#vm-...
https://vuejs.org/v2/guide/co...
原文链接:https://github.com/FrankKai/F...
期待和大家交流,共同进步,欢迎大家加入我创建的与前端开发密切相关的技术讨论小组:
- SegmentFault技术圈:ES新规范语法糖
- SegmentFault专栏:趁你还年轻,做个优秀的前端工程师
- 知乎专栏:趁你还年轻,做个优秀的前端工程师
- Github博客: 趁你还年轻233的个人博客
- 前端开发QQ群:660634678
- 微信公众号: 人兽鬼 / excellent_developers
努力成为优秀前端工程师!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。