Appearance
1. 利用effect实现watch的基础功能
watch的核心原理是利用ReactiveEffect来追踪响应式数据的变化,用到了scheduler调度器,把用户传递的source转换一个函数,因为effect需要依赖一个函数来收集依赖:
ts
function watch(source, cb, options) {
// 1. 创建一个getter函数,用于获取监听的值
let getter
if(isRef(source)) {
getter = () => source.value
} else if(isReactive(source)) {
getter = () => source
} else if(isFunction(source)) {
getter = source
}
// 2. 创建一个effect实例
const effect = new ReactiveEffect(getter)
// 3. 设置调度器,当依赖变化时触发回调
effect.scheduler = job
// 4. 先执行一次,收集依赖
effect.run()
function job() {
const newValue = effect.run()
cb(newValue, oldValue)
oldValue = newValue
}
}- 根据不同的监听源(
ref,reactive对象或函数)创建对应的getter - 使用
ReactiveEffect追踪getter的执行 - 当
getter依赖的响应式发生变化时,通过scheduler在依赖变化时,执行用户传递的回调
2. 如何获取到oldValue
oldValue可以在首次执行effect收集依赖的时候,拿到它:
ts
let oldValue
function job() {
const newValue = effect.run()
cb(newValue, oldValue)
oldValue = newValue
}
oldValue = effect.run()- 首次运行
effect.run()获取初始值并保存为oldValue - 每次依赖更新时,通过
effect.run()获取新值 - 在回调中传入新旧值,并更新
oldValue
3. 停止监听功能
ts
function watch(source, cb, options) {
const effect = new ReactiveEffect(getter)
function stop() {
effect.stop()
}
return stop
}- ReactiveEffect
ts
class ReactiveEffect {
// 表示当前是否激活
active = true
run() {
if(!this.active) {
// 如果未激活,则不收集依赖,直接调用fn
return this.fn()
}
}
stop() {
if(this.active) {
// 开始追踪,会把depsTail设置为undefined
startTrack(this)
endTrack(this)
this.active = false
}
}
}- 返回一个stop函数
- stop函数内部调用
effect.stop()停止响应式追踪
4. immediate功能实现
ts
if(!immediate) {
job() // 立即执行一次
} else {
oldValue = effect.run()
}- 如果设置了
immediate,立即执行一次job函数 - 否则只运行
effect.run()获取初始值
5. once功能实现
ts
if(once) {
const _cb = cb
cb = (...args) {
_cb(...args)
stop()
}
}- 保存原始回调
- 包装新的回调函数,在执行后立即调用stop
6. deep功能实现
ts
if(deep) {
// 如果 deep 为 true,则调用 traverse 函数,递归触发 getter
const baseGetter = getter
// 此处需要考虑递归的层级问题,如果用户传递了层级,根据用户传递的监听层级进行遍历
const depth = deep === true ? Infinity : deep
getter = () => traverse(baseGetter(), depth)
}
function traverse(value, depth = Infinity, seen = new Set()) {
// 如果不是一个对象,或者监听层级到了,直接返回 value
if (!isObject(value) || depth <= 0) {
return value
}
// 如果之前访问过,那直接返回,防止循环引用栈溢出
if (seen.has(value)) {
return value
}
// 层级 -1
depth--
// 加到 seen 中
seen.add(value)
for (const key in value) {
// 递归触发 getter
traverse(value[key], depth, seen)
}
return value
}- 通过
traverse函数递归访问对象的所有属性 - 使用
seen Set防止循环引用 - 支持设置遍历深度
7. onCleanup功能实现
ts
let cleanup = null
function onCleanup(cb) {
cleanup = cb
}
function job() {
if (cleanup) {
cleanup()
cleanup = null
}
const newValue = effect.run()
cb(newValue, oldValue, onCleanup)
}- 提供
onCleanup函数给用户注册清理函数 - 在每次触发回调前执行清理函数
- 执行完成函数后重置
cleanup
8.完整代码实现
ts
function watch(source, cb, options) {
let { immediate, once, deep } = immediate || {}
// 1. 创建一个 getter 函数,用于获取监听源的值
let getter
if((source)) {
getter = () => source.value
} else if (isReactive(source)) {
getter = () => source
} else if (isFunction(source)) {
// 如果source是一个函数,那么getter就等于source
getter = source
}
if(once) {
// 如果once传了,那就保存一份,新的cb等于直接调用原来的,加上stop停止监听
const _cb = cb
cb = (...args) => {
_cb(...args)
stop()
}
}
if(deep) {
const baseGetter = getter
const depth = deep === true ? Infinity : deep
getter = () => traverse(baseGetter(), depth)
}
let oldValue
let cleanup = null
function onCleanup(cb) {
cleanup = cb
}
function job() {
if (cleanup) {
// 看一下是否需要清理上一次的副作用,如果有,则清理,并置空
cleanup()
cleanup = null
}
// 执行effect.run,拿到getter的返回值,不能直接执行,getter。因为要收集依赖
const newValue = effect.run()
// 执行用户回调
cb(newValue, oldValue, onCleanup)
// 下一次oldValue 就等于这一次的newValue
oldValue = newValue
}
const effect = new ReactiveEffect(getter)
effect.scheduler = job
if (immediate) {
job()
} else {
// 拿到oldValue,并且收集依赖
oldValue = effect.run()
}
function stop() {
effect.stop()
}
return stop
}
function traverse(value, depth = Infinity, seen = new Set()) {
// 如果不是个对象,或者监听层级到了,直接返回value
if (!isObject(value) || depth <= 0) {
return value
}
// 如果直接前访问过,直接返回,防止循环引用栈溢出
if (seen.has(value)) {
return value
}
// 层级-1
depth--
// 加到seen
seen.add(value)
// 递归触发getter
for (const key in value) {
traverse(value[key], depth, seen)
}
return value
}