1.创建watch函数
在Vue3中,watch是一个函数,这个函数接收三个参数,前两个是必须的参数,第三个是一个配置参数。
- 第一个参数是需要监听的响应式数据,对于复杂类型的数据可以直接传变量本身或者传入一个函数,该函数返回需要监听的数据,如果是简单类型数据,则必须传入一个函数然后函数返回该数据。
- 第二个参数是一个回调函数,该回调函数会在监听数据发生变化后执行,同时该函数会被注入两个参数,第一个参数为变化后的新值,第二个参数为变化前的旧值。
- 第三个参数是一个配置对象,可以传入一个deep属性,如果为true,则监听的数据变化后,会递归遍历数据,将所有数据都监听一遍。
js
export function watch(source,callback,options = {} as any) {
return doWatch(source,callback,options)
}
2.doWatch函数实现
设置doWatch目的是为了与watch相似的API ,watchEffect准备,便后后续代码书写
- 第一步,判断传入的source是否为函数,如果是函数则调用该函数获取到响应式数据并重新赋值给source,如果不是,则将该函数直接作为getter用于后续创建effect
- 判断当前source是否还是一个对象,如果是对象则说明第一步中传入的函数返回的是一个对象,监听整个对象,或者说传入的并不是函数而直接是一个对象
- 通过第二步判断,我们需要开始创建一个getter函数,同时该函数内部需要递归调用某个函数来实现对象属性的访问,递归的过程需要监控当前递归的层级与当前watch的配置问题,如果watch配置为深度监听,就需要完全遍历对象。
- 创建effect,并传入getter函数作为effect的run函数,同时将callback作为effect的回调函数
js
function doWatch(source,callback,{ deep }) {
let getter;
// 判断传入是否是一个函数
if(isFunction(source)) {
// 判断当前函数返回值是否是简单数据
const result = source()
if(isObject(result)) {
source = result
} else {
getter = source
}
}
//判断当前source是否为对象
if(isObject(source)) {
const reactiveGetter = (source) => traverse(source,deep === false ? 1 : undefined)
getter = () => reactiveGetter(source)
}
// 执行的工具函数
const job = () => {
// 执行逻辑
callback()
}
// 创建依赖对象
const effect = new ReactiveEffect(getter,job)
}
3.实现traverse函数
- 遍历对象,需要判断当前层级是否为1,如果为1,说明是第一层,需要将该对象中的所有属性都监听一遍,否则就递归遍历对象
同时为了防止对象中有循环引用,需要设置一个seen集合,用于判断当前对象是否已经遍历过,如果已经遍历过则直接返回
js
function traverse(source, depth,currentDepth = 0,seen = new Set()) {
// 判断是否为对象
if(!isObject(source)) return source
if(depth) {
if(currentDepth >= depth) {
return source
}
currentDepth++
}
// 判断是否循环引用
if(seen.has(source)) return source
for(let key in source) {
traverse(source[key],depth,currentDepth,seen)
}
seen.add(source)
return source
}
4 .注入新值与旧值**
在effect创建之前,我们创建一个oldValue变量用于存放老值
然后我们主动调用一次effect.run()获取到当前数据
在job调用时我们再次手动调用effect.run获取到新值,然后通过callback函数将新值与老值传入
js
function doWatch(source,callback,{ deep }) {
...
// 保存老值
let oldValue;
// 执行的工具函数
const job = () => {
// 获取新值
const newValue = effect.run()
// 执行逻辑
callback(newValue,oldValue)
oldValue = newValue // 替换老值
}
// 创建依赖对象
const effect = new ReactiveEffect(getter,job)
oldValue = effect.run()
}
5.判断source数据类型
watch只能够监听响应式数据,因此我们需要在getter赋值时来判断当前数据是否是响应式,如果不是响应时则不做处理
还有一种可能就是如果传入的第一个参数是一个函数,第二个参数不是函数或者不传,这就会使用watchEffectAPI,因此我们还需要再判断
js
# apiWatch.ts
function doWatch(source,callback,{ deep }) {
...
// 判断是否是响应式对象
if(isReactive(source)) {
getter = () => reactiveGetter(source)
} else if(isRef(source)) {
getter = () => source.value
}
...
const job = () => {
if(callback) {
// 获取新值
const newValue = effect.run()
// 执行逻辑
callback(newValue,oldValue)
oldValue = newValue // 替换老值
} else {
// watchEffect
}
}
...
// 判断当前是否传入回调函数
if(callback) {
} else {
// watchEffect 处理
}
}
6.watch立即执行与watchEffect
- watch函数的第三个参数可以传入配置项,其中就有一个配置是immediate,该配置的作用是当监听器创建时立即执行回调函数
js
// 判断如果是需要立即执行,则先执行一次job函数,将新旧值传入
if(immediate) {
job()
} else {
oldValue = effect.run()
}
- watchEffect函数与watch函数很相似,函数接收两个参数,第一个参数传入一个回调函数,第二个参数传入一个配置,当第一个函数依赖的数据变化,就会重新执行该函数,本质上就是一个effect。
js
export function watchEffect(getter, options = {}) {
return doWatch(getter,null,options as any)
}
7.doWatch返回值
- 返回一个stop函数,用于停止监听
在这个函数函数中调用effect的stop方法,用于停止监听
js
function doWatch(source,callback,{ deep }) {
...
// 创建依赖对象
const effect = new ReactiveEffect(getter,job)
// 判断是否需要立即执行
if(immediate) {
job()
} else {
oldValue = effect.run()
}
// 返回一个stop函数
return () => {
effect.stop()
}
}