深入理解vue的watch

前言

version: v2.5.17-beta.0

阅读本文需读懂vue数据驱动部分

watch的使用

当改变data值,同时会引发副作用时,可以用watch。比如:有一个页面有三个用户行为会触发this.count的改变,当this.count改变,需要重新获取list值,这时候就可以用watch轻松完成

new Vue({ el: '#app', data: { count: 1, list: [] }, watch: { // 不管多少地方改变count,都会执行到这里去改变list的值 count(val) { ajax(val).then(list => { this.list = list; }) } }, methods: { // 点击+1,count + 1,刷新列表 handleClick() { this.count += 1; }, // 点击重置,count = 1,刷新列表 handleReset() { this.count = 1; }, // 点击随机, count随机数,刷新列表 handleRamdon() { this.count = Math.ceil(Math.random() * 10); } } })

这样的好处就是把所有源头聚集到了watch中,不需要在多个count改变的地方手动去调用方法,减少代码冗余。

watch的多种使用方式

watch的写法有多种,以上案例是最常见的一种方法,接下来介绍所有写法。

传值函数 new Vue({ data: { count: 1 }, watch: { count() { console.log('count改变') } } })

最常见的写法,count改变时将会触发传值的回调函数

传值数组 new Vue({ data: { count: 1 }, watch: { count: [ () => { console.log('count改变') }, () => { console.log('count watch2') } ] } })

传数组,count改变后会依次执行数组内每一个回调函数

传值字符串 new Vue({ data: { count: 1 }, watch: { count: 'handleChange' }, methods: { handleChange(val) { console.log('count改变了') } } })

我们也可以传值字符串handleChange,然后在methods写handleChange函数的逻辑,同样可以做到count改变执行handleChange

传值对象 new Vue({ data: { count: 1 }, watch: { count: { handler() { console.log('count改变') } } } })

可以传值对象,该对象包含一个handler函数,当count改变时,会执行此handler函数,为什么多此一举需要包装一层对象呢?存在即合理,是有其特殊作用的。

传值对象的其他作用

watch为监听属性的变化,调用回调函数,因此,在初始化时,并不会触发,在初始化后属性改变才触发,如果想要初始时也要触发watch,那就需要传值对象,如下:

new Vue({ data: { count: 1 }, watch: { count: { immediate: true, // 加此属性 handler() { console.log('count改变') } } } })

传的对象有immediate属性为true,则watch会立刻触发。

源码分析watch

本节进行源码分析,探索watch的真面貌

初始watch // 初始化 function initState (vm) { vm._watchers = []; var opts = vm.$options; if (opts.data) { initData(vm); } if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); } } // watch初始化 function initWatch (vm, watch) { for (var key in watch) { var handler = watch[key]; if (Array.isArray(handler)) { for (var i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]); } } else { createWatcher(vm, key, handler); } } }

从vue的执行流程,读到了initWatch函数,此函数的用法很清晰,将传入的每一个watch属性执行createWatcher处理。如果传值是数组,则遍历去调用。

下面看一下createWatcher函数

function createWatcher ( vm, expOrFn, handler, options ) { // 如果是对象,则处理 if (isPlainObject(handler)) { // 将对象缓存,给$watch函数 options = handler; handler = handler.handler; } if (typeof handler === 'string') { handler = vm[handler]; } return vm.$watch(expOrFn, handler, options) }

createWatcher中做了兼容处理:

如果handler是个对象,则进行一步转换;

如果handler是字符串,则取vue实例的方法(methods里声明)

最后调用实例的$watch方法

创建Watcher Vue.prototype.$watch = function ( expOrFn, cb, options ) { var vm = this; if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } options = options || {}; options.user = true; var watcher = new Watcher(vm, expOrFn, cb, options); if (options.immediate) { cb.call(vm, watcher.value); } return function unwatchFn () { watcher.teardown(); } };

vm.$watch里是最终实现watch的部分,在这里仍然做了兼容判断,如果是对象,回调createWatcher;接下来就最重要的new Watcher。

$watch的功能其实就是new了一个Watcher,那么,我们在代码里实现的一切响应,都来自于Watcher,下面看一下watch里的Watcher

watchWatcher

Watcher是vue数据驱动核心部分的一员,他承载着依赖收集与事件的触发。下面重点解读一下watch的Watcher实现。

if (typeof expOrFn === 'function') { this.getter = expOrFn; } else { // parsePath去解析expOrFn并返回getter函数 this.getter = parsePath(expOrFn); if (!this.getter) { this.getter = function () {}; } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpwyxw.html