常用用法
方式一:
- 如果state是个对象,那么后续改动这个对象里的属性,回调里拿到的都是新值,也就是说当watch监听对象的时候无法区分前后的新值和老值
- 这种监控是深度监控,无论state的层数有多深,都会监控到。默认就是深度监控
- 但是这种监控方式性能不太好,因为如果state对象的层数太深了,每个属性都会收集这个回调函数
方式二:
- 这种可以监控一个函数的返回值,函数的返回值一变,就会触发回调函数
watch的本质就是一个effect,内部会对用户填写的数据进行依赖收集
接下来我们就来实现watch
我们先新建一个watch.ts文件,导出watch
首先,我们需要判断用户传进来的参数是否是响应式的
- 具备响应式的值都有ReactiveFlag.IS_REACTIVE标识
- 如果用户传进来的是一个响应式的值,那么我们就把它包装成一个函数的返回值
- 然后写一个函数,返回值是一个对象,对象里会去调回调函数,传入新值和老值,被ReactiveEffect类包装getter后,这个job实际上就是getter的调度器,我们之前的文章有讲过
- 然后我们拿到ReactiveEffect的实例,最后调run方法,拿到返回值,这个返回值实际上就是getter的返回值,而getter的返回值就是用户传入的数据,而初次调用的时候,拿到的返回值就是用户初始传入的老值
- 而新值,我们可以在调度器中再执行一次run,也就是再拿一次用户的传入的值,因为调度器是在用户更改数据之后触发的,所以此时取的值一定是新值,然后拿到返回结果作为新值,最后新值和老值作为回调函数的参数
- 调完回调函数后,新值也就变成了老值
问题1:
- 我们不能直接把用户传入的值作为函数的返回值,这样只会监控整个对象,没有意义
- 对用户传入的数据,我们要进行循环,因为只要循环访问到属性,就会触发get,进行依赖收集effect
- 递归进行属性访问,Set防止存在循环引用,值就是对象本身,我们就不用再循环了
处理参数是函数的情况
- 如果参数是函数,我们不用特殊处理了
一个实用场景:当用户向输入框输入的时候,我没呢要根据用户输入的内容,返回搜索结果
我们可能想到的写法:
- watch 输入框的内容,输入框的内容一变化就访问接口,渲染页面
*但是这样会出现一个问题,你先输入一个值,然后再输入一个值,会并发请求两次,第二次有可能会先请求回来,第一次请求的结果会把第二次请求回来的结果干掉
vue3提供了一个机制
伪代码
- 第一调用watch的时候,用户注入一个取消的回调,这个回调初始不执行
- 第二次调用watch的时候会执行第一次注入的回调,会把第一次的clear标识改成true,就会阻止第一次的渲染
- 在频率非常高的情况下,下一次会阻止上一次的渲染,解决并发问题
现在我们就去实现这个onCleanup
- watch方法里增加一个函数onCleanup保存onCleanup回调函数
- 在调度器中进行判断,因为你下一次更改值后才会触发调度器,也就是说调度器是下一次更改值调用的
- 在调度器里首先判断cleanup是否有值,有就调用,然后把onCleanup函数传入watch回调函数中,当你调回调函数的时候,会把自己的cleanup传入进去,保存起来。