Vue响应式

184 阅读2分钟

导言

现在项目还在用的Vue2进行开发,本以为对Vue响应式原理应该理解的很清楚了,但今天有个问题还困扰了一小会,大体是这样的:

// ParentComp.vue
<child-comp :propId="obj.propId"></child-comp>

export default {
  data () {
    return {
      obj: {}
    }
  }
  ...
}

// ChildComp.vue
export default {
  props: {
    propId: {
      type: String,
      default: ''
    }
  }
  watch: {
    propId (id) {
      console.log(id)
    }
  }
}

父组件中传入propId,子组件中通过props接收,是响应式的,并通过watch来监听propId值的变化。一切看起来没问题,也是平时最常用的基本逻辑。然而obj.propId重新被赋值了,子组件propId并没有被watch到,奇了怪了,第一反应是传入值对象的属性不是响应式的?就在被赋值的一处执行下set,仍无济于事,那就不是这个问题了。因为父组件中的逻辑很复杂,被赋值的地方可不止一处,还不好定位到。没办法,只能看下最终改属性的值,在mounted中加个延时器,打印出的数据的确和初始值相同,都是空字符串,说明propId值的变化过程至少是从空字符串到某个值,最后再被赋值成空字符串,所以没触发watch。但令我不解的问题就在这里,虽然初始值和最终值相同,从这个角度来说,不会触发watch,但中间至少有一次有值的赋值,为什么没有触发watch呢?

watch执行

其实这个问题总的来说就是延迟造成的,也就是说不会父变化,子立刻更新,可能有几毫秒甚至几十毫秒的延迟。为什么呢? 在Vue1版本的时候,有个async属性,设置为false的时候,立刻会被执行,但这样对性能方面肯定是不利的。后来到了Vue2版本,虽然源码里面保留了,但只能用于非生产环境,我们看下源码:

export function queueWatcher (watcher: Watcher) { 
  ... 
  if (process.env.NODE_ENV !== 'production' && !config.async) {
    flushSchedulerQueue()
    return 
  } 
  nextTick(flushSchedulerQueue) 
}

非生产环境和async为false时会立刻执行flushSchedulerQueue函数,否则的话就放入nextTick微任务中执行,会存在明显延迟。