这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战
前言
在前边看keep-alive的时候,顺便了解到Vue的组件Vnode是没有children的;是没法像普通的Vnode节点去执行updateChildren函数的,它的更新时机是在diff之前执行了prepatch钩子,在这里去处理子组件更新的。
它的具体处理逻辑如下: prepatch -> updateChildComponent -> vm.$forceUpdate
Vue不光在更新子组件的时候用了$forceUpdate,还把该实例方法暴露给了开发者使用,官方文档是这么介绍该方法的:迫使组件实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
这篇文就来看下$forceUpdate方法是怎么去实现强制更新的??
$foreceUpdate内部实现
$forceUpdate方法定义在src/core/instance/lifecycle.js中
Vue.prototype.$forceUpdate = function () {
const vm: Component = this //当前Vue实例
if (vm._watcher) {
vm._watcher.update()
}
}
原来这个函数定义的这么简单,它内部就是调用vm._watcher.update方法。
对vm._watcher应该是有点印象的,因为前边在看Watcher构造函数部分的时候是有看到过对_watcher的赋值的,现在来具体看下这个_watcher是在哪里传值设置的:
mountCompont
在前边看new Vue的过程的时候,有个函数叫mountCompont,里边有一段注释是关于$forceUpdate的,定义在src/core/instance/lifecycle.js中
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
...
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
...
}
这段注释大概是说new Watcher是为了在Watcher的构造函数内部设置vm._watcher为当时的this实例。因为Watcher的初始patch会调用$forceUpdate,比如子组件的mouted钩子,所以需要依赖vm._watcher已经存在。
再复习下Watcher构造函数对这块的设置,定义在src/core/observer/watcher.js中
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
...
}
在Watcher的构造函数中,如果是渲染Watcher,会把vm._watcher设置为当前的Vue实例。
顺便也复习下update方法:
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
到这里,对$forceUpdate的实现就看完了,接下来看下开发者会在何种场景用$forceUpdate
开发者使用$forceUpdate
看一个例子:
<template>
<div>
<div>
{{ a.b || '初始值' }}
</div>
<button @click="changeData">
修改数据
</button>
</div>
</template>
<script>
export default {
data() {
return {
a: {}
};
},
methods: {
changeData() {
this.a.b = '修改了';
this.$forceUpdate();
}
}
};
</script>
由于a.b并没有被收集依赖,所以直接修改是不能够触发视图更新的,这个时候可以考虑使用$forceUpdate
但是官方也说了另一句话来评价$forceUpdate:
如果你发现你自己需要在Vue中做一次强制更新,99.9%的情况是你在某个地方做错了事情。
所以针对上边的例子最好的方式应该是使用$set来收集a.b的依赖。
总结
-
$forceUpdate内部就是调用渲染Watcher的update方法去更新视图。
-
开发者要慎重使用$forceUpdate去触发强制更新,因为该方法并不能让你的属性变成响应式。