1. watch 的使用

语法

import { watch } from "vue"
watch( name , ( curVal , preVal )=>{ //业务处理  }, options ) ;共有三个参数,分别为:name:需要帧听的属性;(curVal,preVal)=>{ //业务处理 } 箭头函数,是监听到的最新值和本次修改之前的值,此处进行逻辑处理。options :配置项,对监听器的配置,如:是否深度监听。

1.1 监听 ref 定义的响应式数据

<template><div><div>值:{{count}}</div><button @click="add">改变值</button></div>
</template><script>
import { ref, watch } from 'vue';
export default {setup(){const count = ref(0);const add = () => {count.value ++};watch(count,(newVal,oldVal) => {console.log('值改变了',newVal,oldVal)})return {count,add,}}
}
</script>


1.2 监听 reactive 定义的响应式数据

<template><div><div>{{obj.name}}</div><div>{{obj.age}}</div><button @click="changeName">改变值</button></div>
</template><script>
import { reactive, watch } from 'vue';
export default {setup(){const obj = reactive({name:'zs',age:14});const changeName = () => {obj.name = 'ls';};watch(obj,(newVal,oldVal) => {console.log('值改变了',newVal,oldVal)})return {obj,changeName,}}
}
</script>


1.3 监听多个响应式数据数据

<template><div><div>{{obj.name}}</div><div>{{obj.age}}</div><div>{{count}}</div><button @click="changeName">改变值</button></div>
</template><script>
import { reactive, ref, watch } from 'vue';
export default {setup(){const count = ref(0);const obj = reactive({name:'zs',age:14});const changeName = () => {obj.name = 'ls';};watch([count,obj],() => {console.log('监听的多个数据改变了')})return {obj,count,changeName,}}
}
</script>


1.4 监听对象中某个属性的变化

<template><div><div>{{obj.name}}</div><div>{{obj.age}}</div><button @click="changeName">改变值</button></div>
</template><script>
import { reactive, watch } from 'vue';
export default {setup(){const obj = reactive({name:'zs',age:14});const changeName = () => {obj.name = 'ls';};watch(() => obj.name,() => {console.log('监听的obj.name改变了')})return {obj,changeName,}}
}
</script>


1.5 深度监听(deep)、默认执行(immediate)

<template><div><div>{{obj.brand.name}}</div><button @click="changeBrandName">改变值</button></div>
</template><script>
import { reactive, ref, watch } from 'vue';
export default {setup(){const obj = reactive({name:'zs',age:14,brand:{id:1,name:'宝马'}});const changeBrandName = () => {obj.brand.name = '奔驰';};watch(() => obj.brand,() => {console.log('监听的obj.brand.name改变了')},{deep:true,immediate:true,})return {obj,changeBrandName,}}
}
</script>

2. watchEffect 的使用

watchEffect 也是一个帧听器,是一个副作用函数。
它会监听引用数据类型的所有属性,不需要具体到某个属性,一旦运行就会立即监听,组件卸载的时候会停止监听。

<template><div><input type="text" v-model="obj.name"> </div>
</template><script>
import { reactive, watchEffect } from 'vue';
export default {setup(){let obj = reactive({name:'zs'});watchEffect(() => {console.log('name:',obj.name)})return {obj}}
}
</script>


停止侦听
当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
在一些情况下,也可以显式调用返回值以停止侦听:

<template><div><input type="text" v-model="obj.name"> <button @click="stopWatchEffect">停止监听</button></div>
</template><script>
import { reactive, watchEffect } from 'vue';
export default {setup(){let obj = reactive({name:'zs'});const stop = watchEffect(() => {console.log('name:',obj.name)})const stopWatchEffect = () => {console.log('停止监听')stop();}return {obj,stopWatchEffect,}}
}
</script>


清除副作用

有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (场景:有一个页码组件里面有5个页码,点击就会异步请求数据。于是做一个监听,监听当前页码,只要有变化就请求一次。问题:如果点击的比较快,从1到5全点了一遍,那么会有5个请求,最终页面会显示第几页的内容?第5页?那是假定请求第5页的ajax响应的最晚,事实呢?并不一定。于是这就会导致错乱。还有一个问题,连续快速点5次页码,等于我并不想看前4页的内容,那么是不是前4次的请求都属于带宽浪费?这也不好。

于是官方就给出了一种解决办法:
侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参,用来注册清理失效时的回调。
当以下情况发生时,这个失效回调会被触发:

  • 副作用即将重新执行时;
  • 侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,则在组件卸载时)
watchEffect(onInvalidate => {const token = performAsyncOperation(id.value)onInvalidate(() => {// id has changed or watcher is stopped.// invalidate previously pending async operationtoken.cancel()})
})

首先,异步操作必须是能中止的异步操作,对于定时器来讲中止定时器很容易,clearInterval之类的就可以,但对于ajax来讲,需要借助ajax库(比如axios)提供的中止ajax办法来中止ajax。
现在我写一个能直接运行的范例演示一下中止异步操作:
先搭建一个最简Node服务器,3000端口的:

const http = require('http');const server = http.createServer((req, res) => {res.setHeader('Access-Control-Allow-Origin', "*");res.setHeader('Access-Control-Allow-Credentials', true);res.setHeader('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, OPTIONS');res.writeHead(200, { 'Content-Type': 'application/json'});
});server.listen(3000, () => {console.log('Server is running...');
});server.on('request', (req, res) => {setTimeout(() => {if (/\d.json/.test(req.url)) {const data = {content: '我是返回的内容,来自' + req.url}res.end(JSON.stringify(data));}}, Math.random() * 3000);
});
<template><div><div>content: {{ content }}</div><button @click="changePageNumber">第{{ pageNumber }}页</button></div>
</template><script>
import axios from 'axios';
import { ref, watchEffect } from 'vue';
export default {setup() {let pageNumber = ref(1);let content = ref('');const changePageNumber = () => {pageNumber.value++;}watchEffect((onInvalidate) => {// const CancelToken = axios.CancelToken;// const source = CancelToken.source();// onInvalidate(() => {//   source.cancel();// });axios.get(`http://localhost:3000/${pageNumber.value}.json`, {// cancelToken: source.token,}).then((response) => {content.value = response.data.content;}).catch(function (err) {if (axios.isCancel(err)) {console.log('Request canceled', err.message);}});});return {pageNumber,content,changePageNumber,};},
};
</script>

上面注释掉的代码先保持注释,然后经过多次疯狂点击之后,得到这个结果,显然,内容错乱了:


现在取消注释,重新多次疯狂点击,得到的结果就正确了:


除了最后一个请求,上面那些请求有2种结局:

  • 一种是响应的太快,来不及取消的请求,这种请求会返回200,不过既然它响应太快,没有任何一次后续 ajax 能够来得及取消它,说明任何一次后续请求开始之前,它就已经结束了,那么它一定会被后续某些请求所覆盖,所以这类请求的 content 会显示一瞬间,然后被后续的请求覆盖,绝对不会比后面的请求还晚。
  • 另一种就是红色的那些被取消的请求,因为响应的慢,所以被取消掉了。

所以最终结果一定是正确的,而且节省了很多带宽,也节省了系统开销。

副作用刷新时机

Vue 的响应性系统会缓存副作用函数,并异步地刷新它们,这样可以避免同一个“tick”中多个状态改变导致的不必要的重复调用。

同一个“tick”的意思是,Vue的内部机制会以最科学的计算规则将视图刷新请求合并成一个一个的"tick",每个“tick”刷新一次视图,如:a=1; b=2; 只会触发一次视图刷新。$nextTick的Tick就是指这个。

如 watchEffect 监听了2个变量 count 和 count2,当我调用countAdd,你觉得监听器会调用2次?
当然不会,Vue会合并成1次去执行。
代码如下,console.log只会执行一次:

<template><div><div>{{count}} {{count2}}</div><button @click="countAdd">增加</button></div>
</template><script>
import { ref,watchEffect } from 'vue';export default {setup(){let count = ref(0);let count2 = ref(10);const countAdd = () => {count.value++;count2.value++;}watchEffect(() => {console.log(count.value,count2.value)})return{count,count2,countAdd}}
}
</script>

在核心的具体实现中,组件的 update 函数也是一个被侦听的副作用。当一个用户定义的副作用函数进入队列时,默认情况下,会在所有的组件update前执行。

所谓组件的 update 函数是 Vue 内置的用来更新DOM的函数,它也是副作用,上文已经提到过。
这时候有一个问题,就是默认下,Vue会先执行组件DOM update,还是先执行监听器?

<template><div><div id="value">{{count}}</div> <button @click="countAdd">增加</button></div>
</template><script>
import { ref,watchEffect } from 'vue';export default {setup(){let count = ref(0);const countAdd = () => {count.value++;}watchEffect(() => {console.log(count.value)console.log(document.querySelector('#value') && document.querySelector('#value').innerText)})return{count,countAdd}}
}
</script>

点击若干次(比如2次)按钮,得到的结果是:


为什么点之前按钮的innerText打印null?
因为事实就是默认先执行监听器,然后更新DOM,此时DOM还未生成,当然是null。
当第1和2次点击完,会发现:document.querySelector(‘#value’).innerText 获取到的总是点击之前DOM的内容。
这也说明,默认Vue先执行监听器,所以取到了上一次的内容,然后执行组件 update。

Vue 2其实也是这种机制,Vue 2使用 this.$ nextTick() 去获取组件更新完成之后的 DOM,在 watchEffect 里就不需要用this.$nextTick()(也没法用),有一个办法能获取组件更新完成之后的DOM,就是使用:

// 在组件更新后触发,这样你就可以访问更新的 DOM。
// 注意:这也将推迟副作用的初始运行,直到组件的首次渲染完成。
watchEffect(() => {/* ... */},{flush: 'post'}
)

现在设上 flush 配置项,重新进入组件,再看看:


所以结论是,如果要操作“更新之后的DOM”,就要配置 flush: ‘post’。

如果要操作“更新之后的DOM ”,就要配置 flush: 'post'。
flush 取值:pre (默认)post (在组件更新后触发,这样你就可以访问更新的 DOM。这也将推迟副作用的初始运行,直到组件的首次渲染完成。)sync (与watch一样使其为每个更改都强制触发侦听器,然而,这是低效的,应该很少需要)

侦听器调试

onTrack 和 onTrigger 选项可用于调试侦听器的行为。

  • onTrack 将在响应式 property 或 ref 作为依赖项被追踪时被调用。
  • onTrigger 将在依赖项变更导致副作用被触发时被调用。

这两个回调都将接收到一个包含有关所依赖项信息的调试器事件。
建议在以下回调中编写 debugger 语句来检查依赖关系:

watchEffect(() => {/* 副作用 */},{onTrigger(e) {debugger}}
)

onTrack 和 onTrigger 只能在开发模式下工作。

3. 总结

watch 特点

watch 监听函数可以添加配置项,也可以配置为空,配置项为空的情况下,watch的特点为:

  • 有惰性:运行的时候,不会立即执行;
  • 更加具体:需要添加监听的属性;
  • 可访问属性之前的值:回调函数内会返回最新值和修改之前的值;
  • 可配置:配置项可补充 watch 特点上的不足:
    immediate:配置 watch 属性是否立即执行,值为 true 时,一旦运行就会立即执行,值为 false 时,保持惰性。
    deep:配置 watch 是否深度监听,值为 true 时,可以监听对象所有属性,值为 false 时保持更加具体特性,必须指定到具体的属性上。

watchEffect 特点

  • 非惰性:一旦运行就会立即执行;
  • 更加抽象:使用时不需要具体指定监听的谁,回调函数内直接使用就可以;
  • 不可访问之前的值:只能访问当前最新的值,访问不到修改之前的值;

Vue 3 watch 与 Vue 2 watch 对比

  • Vue 3 watch 与 Vue 2 的实例方法 vm.$ watch(也就是 this.$ watch )的基本用法差不多,只不过程序员大多使用 watch 配置项,可能对 $watch 实例方法不太熟。实例方法的一个优势是更灵活,第一个参数可以接受一个函数,等于是接受了一个 getter 函数。
<template><div><button @click="r++">{{ r }}</button></div>
</template><script>
import { ref, watch } from 'vue';
export default {setup() {let r = ref(1);let s = ref(10);watch(() => r.value + s.value,(newVal, oldVal) => {console.log(newVal, oldVal);});return {r,s,};},
};
</script>
  • Vue 3 watch增加了同时监听多个变量的能力,用数组表达要监听的变量。回调参数是这种结构:[newR, newS, newT], [oldR, oldS, oldT],不要理解成其他错误的结构。
<template><div><button @click="r++">{{ r }}</button></div>
</template><script>
import { ref, watch } from 'vue';
export default {setup() {let r = ref(1);let s = ref(10);let t = ref(100);watch([r, s, t],([newR, newS, newT], [oldR, oldS, oldT]) => {console.log([newR, newS, newT], [oldR, oldS, oldT]);});return {r,};},
};
</script>
  • 被监听的变量必须是:A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.也就是说,可以是getter/effect函数、ref、Proxy以及它们的数组。绝对不可以是纯对象或基本数据。
  • Vue 3的深度监听还有没有?当然有,而且默认就是,无需声明。当然,前提是深层 property 也是响应式的。如果深层 property 无响应式,那么即便写上 { deep: true } 也没用。

Vue3中 watch、watchEffect 详解相关推荐

  1. vue3过渡和动画详解

    vue3过渡和动画详解 一.认识动画 二.Vue的transition动画 三.Transition组件的原理 四.class添加的时机和命名规则 五.过渡css动画 六.同时设置过渡和动画 七.过渡 ...

  2. ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route

    前几篇文章我们从dapm的数据结构入手,了解了代表音频控件的widget,代表连接路径的route以及用于连接两个widget的path.之前都是一些概念的讲解以及对数据结构中各个字段的说明,从本章开 ...

  3. Asp.net中GridView使用详解(引)【转】

    Asp.net中GridView使用详解(引) GridView无代码分页排序 GridView选中,编辑,取消,删除 GridView正反双向排序 GridView和下拉菜单DropDownList ...

  4. Linux中iptraf命令详解(IP局域网监控工具)

    2019独角兽企业重金招聘Python工程师标准>>> Linux中iptraf命令详解(IP局域网监控工具) 发布时间:2017-12-27 20:46:03   作者:佚名    ...

  5. ArcGIS Engine中的Symbols详解

    转自原文 ArcGIS Engine中的Symbols详解 本文由本人翻译ESRI官方帮助文档.尊重劳动成果,转载请注明来源. Symbols ArcObjects用了三种类型的Symbol(符号样式 ...

  6. js路由在php上面使用,React中路由使用详解

    这次给大家带来React中路由使用详解,React中路由使用的注意事项有哪些,下面就是实战案例,一起来看一下. 路由 通过 URL 映射到对应的功能实现,React 的路由使用要先引入 react-r ...

  7. Linux中etc目录详解

    Linux中etc目录详解 /etc目录 包含很多文件.许多网络配置文件也在/etc 中. /etc/rc   or/etc/rc.d   or/etc/rc*.d   启动.或改变运行级时运行的sc ...

  8. java 自定义正则表达式_java中正则表达式实例详解

    Java中正则表达式运用实例(参看java中正则表达式运用详解): 测试代码 package test; /** * 在String的matches()方法,split()方法中使用正则表达式. * ...

  9. numpy中reshape方法详解

    numpy中reshape方法详解_zhanggonglalala的博客-CSDN博客_reshape

  10. JavaScript中getBoundingClientRect()方法详解

    JavaScript中getBoundingClientRect()方法详解 getBoundingClientRect() 这个方法返回一个矩形对象,包含四个属性:left.top.right和bo ...

最新文章

  1. PHP(十二)文件操作
  2. Android学习笔记:Activity-ListView
  3. BigData:根据最新2018人工智能行业创新企业Top100名单,绘制AI地区热点图,一目了然,看清哪个是AI最热门城市,以及VC最AI的热门领域
  4. python 一次输入10个数_python 如何一次输入3个整数
  5. 垃圾邮件分类器_如何在10个步骤中构建垃圾邮件分类器
  6. Unity 编辑器内建图标获得
  7. java实现画布上画图
  8. python 菜鸟-python菜鸟教程
  9. 经典排序算法(二)--桶排序Bucket Sort
  10. php系统变量有哪些,php预定义系统变量
  11. GPU — NVIDIA GPU 架构发展史
  12. python爬虫百度文库源码_Python爬取百度文库学习
  13. 数学分析教程 第五章学习感受
  14. HTML5 颜色及透明度
  15. AutoK3s v0.4.8 发布 与 Harvester 梦幻联动
  16. 公司邮箱域名可以定制吗?公司邮箱申请注册?公司邮箱登录入口?
  17. 简易购买电影票系统(Java)
  18. 29.递归三元表达式生成式匿名函数
  19. 使用xbox游戏手柄控制PX4的gazebo仿真
  20. iost主网同步手记

热门文章

  1. Unity 3D做2D坦克大战--敌人自动攻击AI编写
  2. Dede URL优化拼音命名
  3. STM32F103 FPGA架构多轴运动控制器 四轴运动控制器硬件方案
  4. 2021年危险化学品生产单位安全生产管理人员新版试题及危险化学品生产单位安全生产管理人员考试总结
  5. bat 使用 7z 压缩文件夹
  6. java 时间是24小时制吗_java时间24小时制
  7. 疫情又又又来了,看我爬取京东6000款口罩,来看看那一款最适合你
  8. SOFR, So Far ... So Good?
  9. 测试Unity中常用代码的运行所需时间:循环和函数篇 #性能测试 # for,foreach,while循环 #委托事件
  10. 浅析栈指针ESP和帧指针EBP