携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情
4.9 watch的实现原理
在实现完computed之后,作者再把目光移向了watch。我们知道watch就是监听一个响应式数据,它的基本用法如下
const obj = reactive({a:1})
watch(obj,()=>console.log('obj changed'))
obj.a++
不记得在哪里看过,说vue中要尽量少用watch。
其实watch确实也不是必须的,vue作为一个mvvm框架,大部分工作都由其中的vm处理了,也就是viewModel.比如说你method中改变了data,那么对应于template中也会变化,这部分就是由viewModel处理了,因此这时候无需watch。
再比如一个经典的例子,输入框调接口搜索。我们可以在change事件中去调接口,同时记得加上防抖或节流。这时候也不需要watch。
言归正传。我们怎么使用之前已经实现过的方法组合为watch呢。
首先,我们肯定是要使用调度器的
function watch = (source, cb) => {
effect(
// 触发读取,收集依赖
()=>source.a,
//调度器
scheduler(){
cb()
}
)
}
这时候,我们发现,必须要读取才能手机依赖,那么读取的字段就需要从source中获取,那么我们如何知道读取哪些字段呢,而不是像现在这样,写死成了a.换句话说,我们现在这个watch函数不具有通用性.
这时候我们需要手动去收集依赖,同时,需要注意的是,source还有可能是一个getter函数
function traverse(value,seen = new Set()){
const typeName = typeof value
// 首先判断value的类型
if(typeName === 'function'){
return value
}
if(typeName !=== 'object' || value === null || seen.has(value)){
return
}
// 将触发过的值放入Set中,避免多次触发
seen.add(value)
// 这里暂不考虑数组,map等等,其实这里会有相当多的逻辑判断
for(const k in value){
// 递归遍历,触发所有层级的依赖
traverse(value,seen)
}
return value
}
这样就基本完成了一个转换source,触发依赖的函数,我们用它替换source.a
function watch = (source, cb) => {
effect(
// 触发读取,收集依赖
traverse(source),
//调度器
scheduler(){
cb()
}
)
}
我们还知道,watch函数还可以获取旧值和新值,这时候就需要lazy这个属性了.这部分,我建议大家自己思考一下,如何使用lazy。我大概提示一下,我们手动执行effect,就能获取旧值,effect自己执行就能获取新值,这里还有一个小坑,当你们自己写就知道了