深入理解vue的watch

摘要:
有很多方法可以使用手表。上述情况是编写watch的最常见方式。接下来,我们将介绍编写watch的所有方法。让我们来看看createWatcher函数函数createWatcher{//如果它是一个对象,则处理如果{//将对象缓存到$watch函数options=handler;handler=handler;}如果{handler=vm[handler];}returnvm.$watch}createWatcher是兼容的:如果该处理程序是对象,它将在一个步骤中转换;如果处理程序是字符串,则使用vue实例的方法,最后调用该实例的$watch方法来创建WatcherVue。原型$watch=function{varvm=this;if{returncreateWatcher}options=options | |{};options.user=true;varwatcher=newWatcher;如果{cb.call;}返回函数unwatchFn(){watch.teardown();}};vm.$手表是最终实现手表的部件。兼容性判断仍在此处进行。如果它是一个对象,则调用createWatcher;接下来是最重要的新守望者。让我们关注手表的Watcher实现。
深入理解vue的watch

vue中的wactch可以监听到data的变化,执行定义的回调,在某些场景是很有用的,本文将深入源码揭开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中做了兼容处理:

  1. 如果handler是个对象,则进行一步转换;
  2. 如果handler是字符串,则取vue实例的方法(methods里声明)
  3. 最后调用实例的$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 () {};
    }
  }

watch Watcher会执行上面部分,parsePath源码可自行查看,他会将obj.a这种写法兼容, 最终是返回需要监听的属性的getter函数

if (this.computed) {
    this.value = undefined;
    this.dep = new Dep();
  } else {
    // 执行get方法
    this.value = this.get();
  }

拿到getter后,会执行this.get方法:

Watcher.prototype.get = function get () {
  // 加入Dep.target
  pushTarget(this);
  var value;
  var vm = this.vm;
  try {
    value = this.getter.call(vm, vm); // 执行
  } catch (e) {
  } finally {
    popTarget();
    this.cleanupDeps();
  }
  return value
};

以上为get方法,内容简单,但是做的事情举足轻重,他不仅做了值的获取,还做了依赖收集。

pushTarget会将当前watchWatcher赋值到Dep.target中,然后执行this.getter函数,要监听的属性如count会触发他的get钩子,与此同时会进行收集依赖,收集到的依赖就是前面Dep.target也就是当前的watchWatcher

正因为有上面的依赖收集,使count属性有了此watchWatcher的依赖,当this.count改变时,会触发set钩子,进行事件分发,从而执行回调函数

Watcher.prototype.getAndInvoke = function getAndInvoke (cb) {
  var value = this.get();
  if (
    value !== this.value ||
    isObject(value) ||
    this.deep
  ) {
    // set new value
    var oldValue = this.value;
    this.value = value;
    this.dirty = false;
    if (this.user) {
      try {
        cb.call(this.vm, value, oldValue);
      } catch (e) {
        handleError(e, this.vm, ("callback for watcher "" + (this.expression) + """));
      }
    } else {
      cb.call(this.vm, value, oldValue);
    }
  }
};

上面就是this.count改变时,最终调用的方法,在这里会执行this.cb,也就是定义的watch的回调函数,会把value/oldValue传递过去

立即执行的watch

前面说到,watch只会在监听的属性改变值后才会触发回调,在初始化时不会执行回调,如果想要一开始初始化就执行回调,需要传参对象,并immediate为true,实现原理已经在创建Watcher贴出来了

if (options.immediate) {
      cb.call(vm, watcher.value);
}

创建watcher时,如果immediate为真值,会直接执行回调函数

与computed比较

computed是计算属性,watch是监听属性变化,有些场景计算属性做的事情,watch也可以做,当然要尽量用computed去做,为什么?

new Vue({
    data: {
        num: 1,
        sum: 2
    },
    watch: {
        num(val) {
            this.sum = val +  1;
        }
    }
})

watch实现需要声明2个data属性num 和 sum,2个都会加入数据驱动,当num改变后,num和sum都触发了set钩子。
而computed不会,computed只会触发num的set钩子,因为sum根本没有声明,num改变后是动态计算出来的。

免责声明:文章转载自《深入理解vue的watch》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇iOS12中推送通知新特性CC2540开发板学习笔记(九)—— BLE协议简介下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

使用 Vue 开发 scrollbar 滚动条组件

Vue 应该说是很火的一款前端库了,和 React 一样的高热度,今天就来用它写一个轻量的滚动条组件; 知识储备:要开发滚动条组件,需要知道知识点是如何计算滚动条的大小和位置,还有一个问题是如何监听容器大小的改变,然后更新滚动条的位置; 先把样式贴出来: .disable-selection { -webkit-touch-callout: none...

关于vue子组件的数据变了视图不更新的解决办法

原因是因为:    vue不能检测data中数组的变动,如利用索引直接改变一个项的值的时候,利用arr.length修改数组的长度的时候, 还有由于vue2.0 使用的是object.definepropoty进行的数据监听,导致Vue不能检测对象属性的添加和删除。 解决方法: Vue.set() 响应式新增与修改数据此时我们需要知道Vue.set()需要...

在VUE中报Duplicate keys detected: '0'. This may cause an update error的报错原因以及解决办法

问题描述 vue版本 : 2.x 首先Duplicate keys detected: '0'. This may cause an update error是vue发现key不是唯一的而引发的错误. 译为中文大概是 : 检测到重复键 : '0'. '0'可能会导致一个更新错误. 解决办法网上找的其他也有一些.通过手动进行修改:key进而解决,比如说进行拼...

vite vue插件打包配置

import { defineConfig, UserConfigExport, ConfigEnv } from "vite"; import externalGlobals from "rollup-plugin-external-globals"; import vue from "@vitejs/plugin-vue"; import dts f...

VMware vCenter 6.5 安装及群集配置介绍

一、介绍 VMware vCenter Server 提供了一个可伸缩、可扩展的平台,为虚拟化管理奠定了基础。可集中管理VMware vSphere环境,与其他管理平台相比,极大地提高了 IT 管理员对虚拟环境的控制。 VMware vCenter Server:提高在虚拟基础架构每个级别上的集中控制和可见性,通过主动管理发挥 vSphere 潜能,是一...

VMware虚拟机下Ubuntu安装VMware Tools详解

一、安装步骤 1、开启虚拟机,运行想要安装VMware Tools的系统,运行进入系统后,点击虚拟机上方菜单栏的“虚拟机(M)”->点击“安装 VMware Tools”,图片所示是因为我已经安装好了 2、完成第一步后,系统桌面会有一个VMware Tools文件,进入文件目录,可以看到以下目录: 3、使用终端解压VMwareTools-xxxx...