Vue2中watch与Vue3中watch对比和踩坑

devtools/2024/9/24 9:41:47/

上一节说到了 computed计算属性对比 ,虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

Vue2 watch用法

 Vue2 中的 watch 是一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个 property。

Vue2 存在两种监听方式,分别是简单监听和复杂监听

简单监听:监听的是一个回调函数,当监听的值发生改变时,才会执行监听动作。

javascript"><template><h2>当前求和值为:{{ sum }}</h2><button @click="sum++">点击加1</button>
</template><script>export default {name: "TestComponent",data() {return {sum:1}},watch:{sum(newValue, oldValue) {console.log('sum的值变化了',newValue, oldValue);}},
};
</script>

上面的是一个最简单的监听动作,只有在点击按钮 sum 的值变化之后,监听器 watch 才会触发。同时,我们还可以将这个方法放到 methods 中,通过方法名的方式在 watch 中实现监听效果

javascript">  watch:{sum:'sumAdd'},methods: {sumAdd(newValue, oldValue) {console.log('sum的值变化了',newValue, oldValue);}},

深度监听:监听的是一个包含选项的对象。除了包含简单监听的功能之外,还包含深度监听、初始化监听等。

首先,我们可以通过对象形式来实现简单监听的效果,还是按照上面的例子,例如:

javascript">// 其余代码一致
watch:{sum:{handler(newValue, oldValue) {console.log('sum的值变化了',newValue, oldValue);}}
},

通过对象形式实现深度监听 -- deep:true 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深,也就是说即使监听的是一个对象形式的数据,只要对象内部属性发生变化,都能被监听到。

javascript">watch:{sum:{handler(newValue, oldValue) {console.log('sum的值变化了',newValue, oldValue);},deep:true}
},

通过对象形式实现初始化监听 -- immediate:true 该回调将会在侦听开始之后被立即调用,也就是说在组件初始化时,就会监听一次,在数据改变之后继续监听

javascript">watch:{sum:{handler(newValue, oldValue) {console.log('sum的值变化了',newValue, oldValue);},immediate:true}
},

完整的对象监听:深度监听+初始化监听

javascript">watch:{sum:{handler(newValue, oldValue) {console.log('sum的值变化了',newValue, oldValue);},deep: true,immediate:true}
},

在Vue3 中使用 Vue2 的watch

 和 在 Vue3 中使用 Vue2 的computed 计算属性一样,直接使用 watch 配置项即可。

javascript"><template><h2>当前求和值为:{{ sum }}</h2><button @click="sum++">点击加1</button>
</template><script>
import { ref } from "vue";export default {name: "TestComponent",watch: {sum: {handler(newValue, oldValue) {console.log("sum的值变化了", newValue, oldValue);},deep: true,immediate: true,},},setup() {let sum = ref(1);return {sum,};},
};
</script>

当页面第一次渲染时,监听器就执行了一次,这对应的是  -- immediate: true

点击按钮之后,页面渲染,同时监听器也会同步触发。

Vue3 中 watch的基本使用 

和 computed 一样,组合式api在使用时,需要先引入,再使用。

Vue3 中的 watch 是一个函数,接收三个参数,

  1. 第一个参数是需要被监听的数据( 单个数据,数组格式的多个数据),
  2. 第二个参数是回调函数,用来处理监听之后的动作
  3. 第三个参数则是监听配置项( 深度监听、初始化监听 )。

但是和 computed 不一样的是 在 setup 中定义的监听器不需要使用变量接收且 return 返回的,因为 监听是一种行为,而计算属性则是一个值。 

javascript"><template><h2>当前求和值为:{{ sum }}</h2><button @click="sum++">点击加1</button>
</template><script>
//组合式api需要先引入再使用
import { ref ,watch} from "vue";export default {name: "TestComponent",setup() {let sum = ref(1);// 不用接收,不用返回,因为监听是动作,计算属性、响应式数据、函数都是值watch(sum, (newValue, oldValue) => {console.log("sum的值变化了", newValue, oldValue);})return {sum,};},
};
</script>

Vue3 中 watch 的复杂使用方式

上面说的Vue3 中 watch 的简单使用方式,其实就是监听单个 ref 定义的响应式数据。但是 Vue3 中的 watch 可以分为好几种情况:

情况一:通过 watch 监听 ref 定义的单个基础类型响应式数据,也就是上面的例子

情况二:通过 watch 监听 ref 定义的多个基础类型响应式数据,例如

javascript"><template><h2>当前求和值为:{{ sum }}</h2><button @click="sum++">点击加1</button><br><h2>当前msg值为:{{ msg }}</h2><button @click="msg += '!'">点击加!</button>
</template><script>
import { ref ,watch} from "vue";export default {name: "TestComponent",setup() {let sum = ref(1);let msg = ref('你好啊')watch(sum, (newValue, oldValue) => {console.log("sum的值变化了", newValue, oldValue);})watch(msg, (newValue, oldValue) => {console.log("msg的值变化了", newValue, oldValue);})return {sum,msg};},
};
</script>

但是这么写很明显太麻烦了,我想监听多个,那我就需要写多个 watch 监听函数,还不如 Vue2的配置项直接定义一个对象来的方便,所以Vue3 也提供了简便写法,那就是通过数组形式一次性监听多个数据:

javascript">// 通过 [sum,msg] 一次性监听多个数据
watch([sum,msg], (newValue, oldValue) => {console.log("sum或msg的值变化了", newValue, oldValue);
})

同时,我们改变 sum和msg,发现返回的 newValue 和 oldValue 分别是两个数组

  • 第一步:改变 sum ,newValue 数组中 sum 值改变,msg值不变,oldValue 数组中的值就是 sum 和 msg 的初始值
  • 第二步:改变 msg,newValue 数组中 sum 值不变,msg值改变变,oldValue 数组中的值就是 sum 和 msg 的上一次的值

情况三:通过 watch 中的 immediate: true 初始化监听 ref 定义的基础类型响应式数据

javascript">watch(sum, (newValue, oldValue) => {console.log("sum的值变化了", newValue, oldValue);
},{immediate: true})

可以发现,初始化监听成功,在组件初始化, sum 未发生改变时 监听动作就已经执行了。

情况四:通过 watch 监听 ref 定义的对象类型的响应式数据 -- 存在bug( 无法正确获取 oldValue )

我们用 ref 定义一个响应式对象数据,但是这两有两个点需要注意:

  1. 通过 ref 定义的对象数据,其实底层还是通过 reactive 来实现响应式的
  2. 通过 ref 定义的数据是一个 RefImpl对象,在 js 代码中使用时,不会自动解包,需要 .value 
javascript"><template><p>{{person.name}}</p><p>{{person.age}}</p><button @click="person.name += '~'">更改name</button><button @click="person.age++">更改age</button>
</template>export default {name: "TestComponent",setup() {let person = ref({name: "al",age: 28,});watch(person.value, (newValue, oldValue) => {console.log("person的值变化了", newValue, oldValue);});return {person,};},
};

然后我们就会发现下面这么问题:

  1. 当更改 name 属性时,newValue 中的 name 改变了,但是 oldValue 中的 name 也同步变了
  2. 当更改 age 属性时,newValue 中的 age改变了,但是 oldValue 中的 age也同步变了

这和我们想的也太不一样了啊,不是说好了 oldValue 是上一次的值么,怎么还同步更新了呢? 这其实就是 Vue3 的监听bug,暂时官方也没有给出具体的解决办法,但是其实我们在开发过程中也确实不太关注 oldValue 的值。

但是如果你一定想要监听的话,建议把对象拆成单个 ref ,或者把你需要监听的对象中的某个属性单独拆成一个 ref,例如:上面的例子中,我现摘指向单独监听 age,那我就不把age 塞到 person 对象里面了,单独拎出来使用 ref定义。

javascript">let person = ref({name: "al",
});let age = ref(28)watch(age.value, (newValue, oldValue) => {console.log("age的值变化了", newValue, oldValue);
});

如果你觉得 reactive 可以解决这个问题,你也不妨试一试通过 reactive 定义的响应式数据能否达到你想要的效果。

关于 reactive 和 ref 的原理及对比请参考之前的博文 -- Vue2与Vue3响应式原理对比 里面通过源码详细解释了 ref 和 reactive 定义对象数据的底层关系

情况五:深度监听 ref 定义的对象形式的响应式数据 -- 默认开启 deep:true。监听整个对象。可通过配置关闭( 但是在 Vue3.2之前的版本,好像是不能进行配置更改的 )

其实我们按照上面的例子,将数据嵌套多几层,然后改变数据

javascript">let person = ref({name: "al",age: 28,job:{j1: {work: '前端',salary:1}}
});watch(person.value, (newValue, oldValue) => {console.log("person.salary的值变化了", newValue, oldValue);
});

然后改变数据

<template><p>{{ person.job.j1.work }}</p><p>{{ person.job.j1.salary }}</p><button @click="person.job.j1.salary++">涨薪</button>
</template>

我们可以发现,此时 深度嵌套的值也能被监听到。但是 在 Vue2 中监听深度嵌套数据时,我们是需要配置 deep:true  才能实现的 ,但是在 Vue3 中不用开启 deep:true 就可以直接实现深度监听了。

所以我们可以大胆假设,在 Vue3 中,deep:true 是默认开启的,如果我们进行配置关闭,那会有什么结果呢?

javascript">watch(person.value, (newValue, oldValue) => {console.log("person.salary的值变化了", newValue, oldValue);
},{deep:false});    // 关闭深度监听

关闭之后我们可以发现,无法监听到深度嵌套数据的改变了。

但是,有一个问题需要说明,因为我的 Vue  版本是 "vue": "^3.2.13" 所以看起来配置 是生效的。但是如果好像是3.2以下的版本,这个配置是不生效的,只能默认开启 deep:true。

 情况六:监听 ref 定义的对象形式的响应式数据 -- 只监听嵌套对象中的某个基础属性

javascript"><template><p>工作:{{ person.age }}</p><button @click="person.age++">改变年龄</button>
</template>let person = ref({name: "al",age: 28,
});watch(person.value.age, (newValue, oldValue) => {console.log("person.age改变了", newValue, oldValue);
});

但是点击按钮之后,我们发现 控制台上并没有打印出数据,按理来说这是肯定不可能的,Vue肯定会放开这个口子供开发者使用,所以我们需要从使用语法中查找问题。

javascript">// 之前watch 的第一个参数,要么是 变量,要么是数组。
// 但是在这里,我们需要使用一个函数,通过函数的返回值来表明我们需要监听对象中的那个属性
watch(() => person.value.age, (newValue, oldValue) => {console.log("person.age改变了", newValue, oldValue);
});

然后我们就会发现,控制台上已经可以正常打印正确数据了。

情况七:监听 ref 定义的对象形式的响应式数据 -- 只监听嵌套对象中的某些属性

按照情况二和情况七的结合,我们可以大致上了解监听嵌套对象中的某些属性应该怎么做。

javascript">// 通过 数组模式和函数返回模式相结合,实现了监听对象中某些属性
watch([() => person.value.name,() => person.value.age], (newValue, oldValue) => {console.log("name或age改变了", newValue, oldValue);
});

情况八:监听 ref定义的对象形式的响应式数据 -- 深度监听嵌套对象中的值为复杂类型数据的属性

之前的几种监听情况大致上可以分为

  • 监听 ref 定义的基础类型数据
  • 监听 ref 定义的对象类型数据 --
    • 监听的是整个对象( 包括深度嵌套对象 )
    • 监听的是对象( 包括深度嵌套对象 )中的某个属性 -- 基础类型属性
    • 监听的是对象( 包括深度嵌套对象 )中的某几个属性 -- 都是基础类型属性

如果我们现在监听的是 深度嵌套对象中某个值为复杂类型数据的属性时,那需要怎么做呢?

我们还是用之前的数据来进行测试

javascript">let person = ref({name:'al',job:{j1: {work: '前端',salary:1}}
});

然后我们按照 情况六的模式来进行监听

javascript"><template><p>工作:{{ person.job.j1.salary }}</p><button @click="person.job.j1.salary++">涨薪</button>
</template>// 监听属性通过函数返回
watch(() => person.value.job, (newValue, oldValue) => {console.log("job中的数据该变了", newValue, oldValue);
});

发现此时并没有监听成功。按理来说这也不应该啊,我是按照之前的情况实现的啊,只不过是把监听的数据类型该变了,怎么就不行了呢?

此时我们回忆一下,在我们监听完整的对象时,默认开启 deep:true 。但我们监听的是整个对象,而不是对象中的某个属性。

如果我们监听的是对象中的某个对象属性时,这一套就不适用了,所以我们需要手动配置 deep:true

javascript">watch(() => person.value.job, (newValue, oldValue) => {console.log("name或age改变了", newValue, oldValue);
},{deep: true});

此时改变  salary 后,控制台上能打印出数据,表atch功

watch 监听 ref 与 reactive 定义的数据使用方式区别

在之前的例子中我们定义数据都是使用的 ref, 虽然我有说 ref 定义的对象类型实际上底层还是通过 reactive 来实现响应式,但在 watch 监听时,使用方式还是存在区别的。

分别用 ref 和 reactive 定义深层嵌套的响应式数据

javascript">let person1 = ref({job: {j1: {work: "前端",salary: 1,},},
});let person2 = reactive({job: {j2: {work: "后端",salary: 2,},},
});

 然后使用 watch 分别监听。这里我们就可以看出区别在哪了。

javascript">watch(person1.value,(newValue, oldValue) => {console.log("j1中salary改变了", newValue, oldValue);},
);watch(person2,(newValue, oldValue) => {console.log("j2salary改变了", newValue, oldValue);},
);

ref 定义的数据需要解包,而 reactive 定义的数据则可以直接使用。这一点可以在 之前的博文 ref函数与reactive函数的对比 中可以详细了解一下。 

这是因为,通过 ref 转化的复杂数据,是一个 refImpl实例对象,其中 value属性的值才是真的响应式数据,是通过 reactive 方法中的 Proxy 代理对象实现响应式。这相比 reactive 直接转化就多了一层 refImpl 实例。所以在 js脚本中使用数据时,需要 .value 解包。

然后,我们参考一下情况八,其实对于 ref 定义的深层嵌套对象我们也可以通过手动配置 deep:true 的形式来实现深度监听

javascript">watch(person1,(newValue, oldValue) => {console.log("j1中salary改变了", newValue, oldValue);},{ deep: true }
);

总结

  1. Vue3 中能够使用 Vue2 的模式来实现 watch监听 动作
  2. Vue3 中的 watch 因为是组合式api,所以也需要先引入再使用,和 computed 一致
  3. Vue3 中的 watch 是一个函数,接收三个参数:                                                                          参数一:需要监听的数据,参数类型可以是变量,数组或函数
      参数二:监听的回调函数,接收两个参数,分别代表新值和旧值
      参数三:一个对象,包含复杂监听的配置,例如深度监听 ( dep:true ),初始化监听( immediate: true )
  4. Vue3 中 setup 中的 watch 不需要使用变量接收,也不用返回,因为 watch 监听是动作,而 computed 计算属性最终返回的是值
  5. Vue3 的watch 存在几种情况需要注意:
    1. 通过 ref 定义的简单类型数据,可以直接实现监听,无bug -- 情况一 & 情况二 & 情况三
    2. 通过 ref 或 reactive 定义的复杂类型数据在监听时存在bug:
      1. 无法正确获得 oldValue -- 情况四
      2. 监听的是完整对象,则强制开启深度监听 -- 情况五
      3. 监听的是对象中的某个基础数据类型属性,则需要通过函数返回该属性 -- 情况六 & 情况七
      4. 监听的是对象中的某个值为复杂类型数据的属性,则需要通过函数返回,且需要手动配置 deep:true 实现深度监听 -- 情况八
  6. Vue3 中的watch 在监听 ref 和 reactiv定义的响应式数据时,使用方式是存在区别的,那就是 ref 定义的数据,在使用时是需要解包的,而 reactive 则是不需要。

http://www.ppmy.cn/devtools/96758.html

相关文章

【转】谭荣兵带队到华企网安(北京)科技有限公司考察交流

1月17日&#xff0c;谭荣兵带队到华企网安(北京)科技有限公司考察交流。 图为交流会现场。 武杰 摄 在华企网安(北京)科技有限公司&#xff0c;谭荣兵详细了解企业发展历程、未来发展方向及主要服务项目等情况&#xff0c;并就网络信息处理、大数据助力社会治理等方面与企业负…

JS中链式调用的实现和应用

你好同学&#xff0c;我是沐爸&#xff0c;欢迎点赞、收藏和关注。个人知乎 不知你是否注意过&#xff0c;链式调用在编程中还是很普遍的&#xff0c;无论是原生JavaScript还是在一些框架和库中&#xff0c;都会看到它们的身影。今天我们一探究竟&#xff0c;看下链式调用是怎么…

springBoot+ druid配置多数据源

springBoot druid配置多数据源 1.在yml加&#xff1a; spring:#1.JDBC数据源datasource:druid:first:username: PYpassword: ral2024url: jdbc:mysql://localhost:3306/mysql?serverTimezoneUTC&characterEncodingutf8&useUnicodetrue&useSSLfalsedriver-class-n…

【数据结构】归并排序

1、介绍 归并排序&#xff08;merge sort&#xff09;是一种基于分治策略的排序算法&#xff0c;包含“划分”和“合并”阶段。 划分阶段&#xff1a;通过递归不断地将数组从中点处分开&#xff0c;将长数组的排序问题转换为短数组的排序问题。 合并阶段&#xff1a;当子数组…

1.Linux_常识

UNIX、Linux、GNU 1、UNIX UNIX是一个分时操作系统&#xff0c;特点是多用户、多任务 实时操作系统&#xff1a;来了请求就去解决请求 分时操作系统&#xff1a;来了请求先存着&#xff0c;通过调度轮到执行时执行 2、Linux Linux是一个操作系统内核 发行版本&#xff1…

C++ //练习 17.5 重写findBook,令其返回一个pair,包含一个索引和一个迭代器pair。

C Primer&#xff08;第5版&#xff09; 练习 17.5 练习 17.5 重写findBook&#xff0c;令其返回一个pair&#xff0c;包含一个索引和一个迭代器pair。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 vector<vector<Sal…

测试流程自动化实践!

测试流程自动化的最佳实践涉及多个方面&#xff0c;旨在提高测试效率、确保测试质量&#xff0c;并降低测试成本。以下是一些关键的实践方法&#xff1a; 1. 明确测试目标 确定测试范围&#xff1a;在开始自动化测试之前&#xff0c;需要明确哪些功能、模块或场景需要被测试。…

Keepalived 高可用集群详解和配置

Keepalived 高可用集群 集群类型 1、LB&#xff08;Load Balance&#xff09;&#xff1a;负载均衡 LVS&#xff1a;四层负载均衡 HAProxy&#xff1a;七层/四层 负载均衡 nginx&#xff1a;七层负载均衡 (http/upstream,stream/upstream) 2、HA&#xff08;High Availa bili…