watch侦听器
什么是watch侦听器?
它之所以叫侦听器,是因为它可以侦听一个或多个响应式数据源数据,并再数据源变化时调用所给的回调函数。 就是你传给watch侦听器一个响应式变量,然后当这个变量变化时,自动触发一个你定义的函数,可以理解为就像一个人被监控了一样,只要这个人一动,摄像头就会报警。
实例演示
先实现一个 Vue2 侦听器小例子
<template>
<button @click="sum++">增加数值</button>
</template>
<script setup>
export default {
data() {
return {
sum: 1,
}
},
watch: {
// New: 新值 | Old: 老值
sum(New, Old) {
console.log(`新值:${New} ——— 老值:${Old}`)
},
}
}
</script>
再看Vue3中如何实现
1.差别
首先我们需要明白,在不同版本中,watch所能实现的功能有所不同,在Vue3中,watch特性进行了一些改变和优化。与computed不同,watch通常用于监听数据的变化,并可以执行一些副作用,例如 发送网络请求、更新DOM等
2.具体模版格式
watch(source, callback, options)
其中,source表示要监听的数据,可以是一个响应式的数据对象、一个计算属性或一个方法的返回值;callback表示当数据发生变化时要执行的回调函数;options表示watch的一些配置选项,例如immediate、deep、flush等。
3.实例
<template>
<button @click="sum++">增加数值</button>
</template>
<script setup lang="ts">
import { watch, ref } from 'vue'
const sum=ref(1);
// New: 新值 | Old: 老值
watch(sum,(New, Old)=>{
console.log(`新值:${New} ——— 老值:${Old}`)
})
</script>
对于reactive转化的响应式数组,下述也有一个简单的例子
<template>
<button @click="sum.value++">增加数值</button>
</template>
<script setup lang="ts">
import { watch,reactive} from 'vue'
// 响应式变量
const sum = reactive({
value: 1
})
// watch是一个函数
// 第一个参数: 要监听的响应式变量(数据源)
// 第二个参数:执行的回调函数
// 第三个参数: 配置(例如deep深度监听/immediate是否立即监听等)
// 注意:当监听一个对象时,第一个参数数据源必须是函数且返回一个值即 "() => 对象.要监听的属性"
watch(() => sum.value, (New, Old) => {
console.log(`新值:${New} ——— 老值:${Old}`)
})
</script>
如何监听复杂数据
复杂数据是指对象套对象,对象里再套数组等等。那么这种情况怎么深度监听呢?只需要配置一个叫 deep 的属性为 true。
<template>
<button @click="sum.car.config.isChina = !sum.car.config.isChina">改变数据</button>
</template>
<script setup lang="ts">
import { watch, ref ,reactive} from 'vue'
// 复杂嵌套响应式变量
const sum = reactive({
car: {
name: '长安汽车',
money: 15000,
config: {
color: 'black',
isChina: true,
}
}
})
// 监听复杂响应式对象
watch(() => sum.car, (New, Old) => {
console.log(`新值:${New.config.isChina} ——— 老值:${Old.config.isChina}`)
}, { deep: true })
</script>
监听多个ref的值,采用数组形式
<template>
<button @click="one++">改变 one 数据</button>
<button @click="two.value++">改变 two 数据</button>
</template>
<script setup lang="ts">
import { watch, ref ,reactive} from 'vue'
// 变量1
const one = ref(0)
// 变量2
const two = reactive({
value: 10
})
// 监听多个变量
// 第一个参数变为数组形式,每个下标对应一个要监听的变量
// 第二个参数的函数传参改为每项数组形式,每个数组对应新旧值的参数
watch([one, () => two.value], ([oneNew, twoNew], [oneOld, twoOld]) => {
console.log(`one: ${oneNew}(新) ——— ${oneOld}(旧)`);
console.log(`two: ${twoNew}(新) ——— ${twoOld}(旧)`);
console.warn("——————————————————————————————————————————————");
});
</script>
侦听器是惰性的
正如前文所说,当我们写好 watch 监听后, 它不会主动触发监听函数,而是当触发某些事件,数据发生改变时才触发。刷新页面,并不会触发监听函数,这就叫做惰性,控制台没有任何输出。假如我就想初次进入页面的时候就让它执行,让它勤奋点,这也是有办法可以做到的,只需要一个配置项即可。
<template>
<button @click="sum++">增加数值</button>
</template>
<script setup lang="ts">
import { watch,ref} from 'vue'
// 响应式变量
const sum = ref(1)
// 勤奋了
watch(sum, () => {
console.log('我触发啦!')
}, { immediate: true })
</script>
箭头函数
关于上文中有几处地方我们可能感到疑惑,例如
watch(() => sum.car, (New, Old) => {
watch(sum, () => {
console.log('我触发啦!')
这个()=>箭头函数到底是什么,我们又该如何去理解,下面我们来简单解释一下
任何可以使用函数表达式的地方,都可以使用箭头函数,并且它的语法比传统的函数表达式更加简洁。可以理解为普通函数表达的另一种方式。
简单语法实例
// 函数表达式
let functionExpressionSum = function(a, b) {
return a + b;
}
// 箭头函数
let arrowSum = (a, b) => {
return a + b;
}
//更常见的写法(写到一行)
let arrowSum = (a, b) => { return a + b; }
如果只有一个参数,可以不用括号。只有没有参数或多个参数的情况下,才需要使用括号。
// 只有一个参数,可以不用括号,以下两种写法都有效
let double = (x) => { return 2 * x; };
let triple = x => { return 3 * x; };
// 没有参数需要括号
let getRandom = () => { return Math.random(); };
// 多个参数需要括号
let sum = (a, b) => { return a + b; };
// 无效的写法:
let multiply = a, b => { return a * b; };
如果函数体的返回值只有一句,可以省略大括号(省略大括号会隐式返回这行代码的值)。
// 以下两种写法都有效,而且返回相应的值
let double = (x) => { return 2 * x; };
let triple = (x) => 3 * x;
// 无效的写法
let multiply = (a, b) => return a * b;
如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字
// 最常见的就是调用一个函数
let fn = () => void doesNotReturn();
由于其更简洁的语法,箭头函数的一个用处就是简化回调函数。
// 普通函数写法
var result = arr.sort(function (a, b) {
return a - b;
});
// 箭头函数写法
var result = arr.sort((a, b) => a - b);
简单解释:
函数通过返回a - b
来决定排序顺序。如果a - b
的结果小于0,则a
会被排在b
之前;如果结果大于0,则b
会被排在a
之前;如果结果等于0,则a
和b
的相对位置不变。
箭头函数使用时,左侧是参数列表(可以省略括号当只有一个参数时,但这里是两个参数所以括号不可省略),右侧是函数体(如果函数体只有一行且不需要返回语句,可以省略大括号和return
关键字)。
箭头函数没有自己的this
所有函数在执行时,会创建一个函数执行上下文,普通函数的执行上下文中会有一个变量this,而箭头函数没有,箭头函数不会创建自己的this对象,只会继承在自己作用域的上一层this。
var id = 'Global'
// 箭头函数定义在全局作用域
let fun1 = () => {
console.log(this.id)
}
fun1() // 输出为'Global'
对比一下理解
//箭头函数
const obj = {
getArrow() {
return () => {
console.log(this === obj);
};
}
}
//等含义转换后的函数
var obj = {
getArrow: function getArrow() {
var _this = this; //引用外层的this
return function () {
console.log(_this === obj);
};
}
};
转换后的ES5版本清楚地说明了,箭头函数里没有自己的this,而是引用外层的this。
箭头函数的this不会改变
前文讲到,箭头函数没有自己的this,所以箭头函数中this的指向在它定义时就已经确定了,之后不会改变。
var name = 'GLOBAL';
var obj = {
name: '南木元元',
getName1: function(){
console.log(this.name);
},
getName2: () => {
console.log(this.name);
}
};
obj.getName1(); // 输出结果为'南木元元'
obj.getName2(); // 输出结果为'GLOBAL'
对象obj的方法二是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向外层定义的this所指对象。所以其实定义对象的方法是不适合使用箭头函数的。
箭头函数没有prototype属性
let fn = function(name) {
console.log(name);
}
let fn2 = name => console.log(name);
console.log(fn.prototype);
console.dir(fn2.prototype);//输出结果为undefiend
箭头函数不能作为构造函数
上面说了,箭头函数没有自己的this,没有prototype属性,所以也就不能用作构造函数,即不可以对箭头函数使用new
命令,否则会抛出错误。(这里的构造函数模式可以参考已经学习过的c++语言,this指针也可以类比理解)
let fn = (name, age) => {
this.name = name;
this.age = age;
}
// 报错
let p = new fn('南木元元', 18);
箭头函数不能使用arguments对象
arguments是一个对应于传递给函数的参数的类数组对象。
arguments是在所有普通函数中都可用的一个类数组对象,类数组不是数组,而是类似数组的对象,它除了length属性和索引之外,不能调用数组的方法。
所以通常会使用Array.prototype.slice.call(arguments)/Array.from(arguments)/[...arguments]的方式,将它转换成一个数组。
let fn = function () {
console.log(arguments);
console.log(Array.prototype.slice.call(arguments));
}
fn('param1', 'param2');
箭头函数不可以使用arguments对象,该对象在函数体内不存在。
箭头函数不能用作Generator函数
箭头函数内部不可以使用yield命令,因此箭头函数不能用作Generator函数。
let fn = function *() {
yield '南木元元';
}
let p = fn();
console.log(p.next());
箭头函数不适用的场景
一、对象方法,且方法内部使用this
var name = 'GLOBAL';
var obj = {
name: '南木元元',
getName: () => {
console.log(this.name);
}
};
obj.getName(); // 'GLOBAL'
上述代码中,调用obj.getName()方法时,如果是普通函数,该方法内部的this指向调用它的那个对象;如果写成上面那样的箭头函数,使得this指向了全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致getName箭头函数定义时的作用域就是全局作用域。
二、需要动态this
var button = document.getElementById('btn');
button.addEventListener('click', () => {
console.log(this); //由于使用了箭头函数,this会指向全局对象Window
});
上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。
结合财务管理微信小程序代码
watch侦听器
在src\pages\bill\asset\manage.vue文件中,我们可以看到
watch: {
type(val) {
// console.log("账单类型", val);
this.getCategoryGroups(val);
},
type
后面跟着的是一个函数,这个函数会在type
的值发生变化时被调用。函数接收一个参数val
,这个参数就是type
变化后的新值。然后,调用this.getCategoryGroups(val);
,将新的type
值作为参数传递给getCategoryGroups
方法。这里getCategoryGroups
是一个已经定义在Vue实例中的方法,可通过API去访问对应的账单分类,用于根据传入的账单类型(type
)来执行一些逻辑,比如获取对应的分类组信息。在watch
中的函数里,this
关键字指向的是Vue实例本身,这使得可以访问Vue实例的数据和方法。
箭头函数 ()=>
在src\pages\bill\asset\edit.vue文件中,我们可以看到
dynamicHeight() {
let that = this;
uni.getSystemInfo({
success(res) {
let pH = res.windowHeight;
let query = uni.createSelectorQuery().in(that);
query.select("#edit-header").fields({ size: true });
query.select("#edit-input").fields({ size: true });
query.select("#save-btn").fields({ size: true });
try{
query.exec((data) => {
console.log(data);
data.map((i) => {
that.scrollHeight += i.height;
});
that.scrollHeight = pH - that.scrollHeight;
// console.log(that.scrollHeight);
});
}
catch(err){
that.scrollHeight = pH - 210;
}
},
});
},
其中两处都用到了箭头函数分别将data和i作为参数进行传递,并且因为箭头函数中无法灵活运用this指针,所以特地创建了that指针并让他指向this,以防定义模糊,造成系统执行代码是出错,也方便读者更好的理解代码。
拓展
关于watch:Vue3 - watch 侦听器(保姆级详细使用教程)_vue3 watch-CSDN博客
箭头函数:一篇讲透:箭头函数、普通函数有什么区别_箭头函数和普通函数的区别-CSDN博客
上述博客中有更详细的案例和拓展,请大家空闲时间可以在仔细看看,助于代码理解。