vue中实现watch都是基于核心模块
reactive、effect,当响应式数据发生变更,对应effect会执行
实现常见的两种使用方式
直接使用
这种性能比较差一些,需要递归取值。
const state = reactive({name:"jym"})
watch(state,()=>{
console.log('数据变化')
})
函数返回
const state = reactive({name:"jym"})
watch(state,()=>{
console.log('数据变化')
})
实现
参数处理
import { isReactive } from "vue"
const traverse = (target,seen = new Set())=>{
if (seen.has(target)) return;
if(target !== null && typeof target === 'object'){
seen.add(target);
for (const key in value) {
traverse(target[key], seen); // 递归遍历
}
}
return target
}
function watch(source,cb){
let getter;
if(isReactive(source)){
getter = ()=> traverse(source)
} else if(typeof source === 'function') {
getter = source
}
}
使用effect来处理数据变更回调
export function watch(source, cb) {
...
let oldValue;
// 创建一个effect,对应wacth
let outerCb = () => {
let newValue = effect.run();
cb(newValue, oldValue);
oldValue = newValue
}
const effect = new ReactiveEffect(getter, outerCb);
oldValue = effect.run() // 会执行getter,并且返回getter的返回值
}
思路梳理
- 响应式数据,在取值的时候通过
proxy代理拦截会发生依赖收集,哪个effect使用了该数据,那么对应的依赖dep就会收集这个effect。 - 当数据触发更新,会找到该数据对应属性的
dep(里面存储所有依赖该属性的effect)取出所有effect挨个执行一次。 - 给
watch创建其对应的effect,让里面触发取值行为的数据都收集到此effect - 在数据变更时,会执行
outerCb,里面包含用户传递进来的cb - 传递新老数据进去即可
问题:参数处理,为啥要递归遍历对象呢?
用户传递进来无论是对象,还是函数都会被包装成函数。那这样存在一个问题,传递进来的对象,在effect执行的时候。这个响应式对象并没有取值,也不会触发属性拦截器里面的依赖收集。这样就算state里面的数据变化,当前watch对应的effect并不会执行。所以需要在函数里面。
但是当我们遍历对象,发生取值行为,那么响应式数据对象,就会收集当前的依赖,在数据发生变更的时候,再次执行effect.run。即执行cb函数