watch方法的实现
Vue3中的watch方法用来监测数据的变化,然后执行某些操作,如监测用户的输入
原生方法的使用
- 如果监控的是一个对象,则无法拿到新值和旧值
const { effect, reactive, computed, watch } = Vue
// const { effect, reactive,computed } = VueReactivity
const state = reactive({
firstName: 's',
lastName: 'x'
})
watch(state, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
setTimeout(() => {
state.lastName = 'xxx'
}, 1000);
输入newValue和oldValue结果都为改变后的值
- 如果监控的是一个对象,则会深度监控,即迭代监控对象的每一个属性,因此要尽量避免使用对象,减少资源浪费
- 监控的是某个属性,则需写成函数式
const { effect, reactive, computed, watch } = Vue
// const { effect, reactive,computed } = VueReactivity
const state = reactive({
firstName: 's',
lastName: 'x'
})
watch(()=>state.lastName, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
setTimeout(() => {
state.lastName = 'xxx'
}, 1000);
- 其第二个参数可以传入三个参数,第三个参数是节流控制参数,方式数据多次改变异步并发
- 当第一次改变state.age时,onCleanup中的回调不执行
- 第二次改变时,执行onCleanUp中的回调,此时如果上一步的异步还在执行,则会使其不发生作用
const state = reactive({ flag: true, name: 'jw ', age: 30 })
let i = 2000;
function getData(timer) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(timer)
}, timer);
})
}
// 调用下一次的watch会执行上一次的onCleanup
watch(() => state.age, async (newValue, oldValue,onCleanup) => {
let f = false;
onCleanup(()=>{ // 当我们把age= 32的时候会执行第一次的onCleanup中的回掉
f = true;
})
i-=1000;
let r = await getData(i); // 2000
!f && (document.body.innerHTML = r);
}, { flush: 'sync' });
state.age = 31; // f = true 1s后应该渲染1000
state.age = 32; // f = false 0s后显示0
新值和旧值会输出
watch方法的实现
- watch传入两个参数,第一个参数为要监听的属性source,第二个参数为属性变化后的回调cb
- 判断source类型,若是Proxy,则深度监控,交给监控函数,若是函数,则执行下一步
- 创建job函数
- 函数首先判断是否存在onCleanUp回调,存在则执行
- 创建ReactiveEffect实例,第一个参数为source,第二个参数为job
- 运行一次ReactiveEffect实例,获取原先值oldValue
- 在job中运行ReactiveEffect实例,获取新值newValue,运行cb
- 将newValue赋值给oldValue
import { isFunction, isObject } from "@vue/shared";
import { isReactive } from "./baseHandler";
import { ReactiveEffect } from "./effect";
function traversal(value,set = new Set()){
if(!isObject(value)){
return value
}
if(set.has(value)){
return value
}
set.add(value)
for(let key in value){
traversal(value[key],set)
}
return value
}
export function watch(source,cb){
let get = null;
let cleanUp = null;
let oldValue = null;
if(isReactive(source)){
get = ()=>traversal(source)
}else if(isFunction(source)){
get = source
}
const onCleanUp = (fn)=>{
this.cleanUp = fn
}
let job = () => {
cleanUp && cleanUp()
let newValue = effect.run
cb(oldValue,newValue,onCleanUp)
oldValue = newValue
}
let effect = new ReactiveEffect(get,job)
oldValue = effect.run()
}