Vue源码分析之$forceUpdate

·  阅读 636

这是我参与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去触发强制更新,因为该方法并不能让你的属性变成响应式。

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改