likes
comments
collection
share

一次 vue2 Watch 监听失效引发的对 Watch 的思考

作者站长头像
站长
· 阅读数 28

bug 场景

先来说说 bug 遇到的场景,项目是 vue2 + ts ,一个简单的父子组件通信,传递的是一个对象,然后我需要监听对象中的某个属性,当他变化时,我需要做一些处理。类似下面的代码

father.vue

<template>
    <Child :pen="obj"/>
</template>
···省略部分代码

child.vue

···省略部分代码
<script>
export default class Child extends Vue {
  @Watch('pen.dropDown')
    onWatchList(val, oldVal) { 
      ···
    }
};
</script>
···省略部分代码

但是我发现有时候 watch 并没有起作用,也就是我传递的数据坚挺的那个属性变了,但是并没有做处理。后来分析定位 bug 的时候,发现当在构建 child 实例的时候,传递的对象里边没有 dropDown 属性,后来传递的对象有 dropDown 属性,但是 watch 回调并没有执行

源码分析

vue\src\core\instance\state.js 49行, 在实例化组件的时候会初始化 watch,只有这一次机会 。

export function initState (vm: Component) {
  ···
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

vue\src\core\instance\state.js 293行,初始化 watch 是用 for in遍历组件的 watch 配置,并调用 createWatcher 方法处理。并且会判断坚挺的对象是不是数组类型,如果是数组类型需要遍历数组对每个元素调用 createWatcher 方法处理。

function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {// 判断是否是数组
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}

vue\src\core\instance\state.js 306行,这里会调用 vm.$watch 内置方法处理。

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}

bug 原因猜想

可以看到在初始化的时候是使用for in 遍历 watch 的配置,由于 dropDown 属性不存在会拿到 undefined,所以 watch 实际是监听的 undefined,它不会改变,当你再次传递数据给子组件时,虽然有了 dropDown 属性,但是并没有监听它的回调,所以不会对 dropDown 属性做出处理。

解决方案

方案一

在传数据时处理,保证一定有这个属性,这样就能成功监听该属性。

方案二

监听另一个一定存在的属性,并且要保证该属性的变化与 dropDown 属性的变化是同步的,这样当该属性变化时就可以执行回调,处理 dropDown。

我是孤城浪人,一个正在前端路上摸爬滚打的菜鸟,期待你的关注。