本篇文章为学习笔记,都是一个字一个字敲出来的,如果有和视频重叠部分,侵权行为告知立马删除。
watch api用法
1.侦听getter函数
代码语言:javascript复制const text = reactive({
a: 1
})
watch(() => text.a,(newValue,oldValue)=>{
//当text.a发生变化时,会触发此回调函数
console.log('oldValue: ', oldValue);
console.log('newValue: ', newValue);
});
2.侦听响应式对象
代码语言:javascript复制const text = ref(0)
watch(text,(newValue,oldValue)=> {
//当text.value发生变化时,会触发此回调函数
console.log('oldValue: ', oldValue);
console.log('newValue: ', newValue);
})
3.什么是侦听器 当侦听的对象或函数发生了变化则自动执行某个回调函数,与副作用effect函数很像,那它的内部是否依赖effect?
watch Api的具体实现:
代码语言:javascript复制function watch<T = any, Immediate extends Readonly<boolean> = false>(
source: T | WatchSource<T>,
cb: any,
options?: WatchOptions<Immediate>
): WatchStopHandle {
if (__DEV__ && !isFunction(cb)) {
warn(
``watch(fn, options?)` signature has been moved to a separate API. `
`Use `watchEffect(fn, options?)` instead. `watch` now only `
`supports `watch(source, cb, options?) signature.`
)
}
return doWatch(source as any, cb, options)
}
从源码中看到,watch Api内部调用了doWatch函数,判断cb是否是一个函数,如果不是,则使用effect APi
1.标准化source的流程
代码语言:javascript复制 const warnInvalidSource = (s: unknown) => {
warn(
`Invalid watch source: `,
s,
`A watch source can only be a getter/effect function, a ref, `
`a reactive object, or an array of these types.`
)
}
代码语言:javascript复制function traverse(value: unknown, seen?: Set<unknown>) {
if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
return value
}
seen = seen || new Set()
if (seen.has(value)) {
return value
}
seen.add(value)
if (isRef(value)) {
traverse(value.value, seen)
} else if (isArray(value)) {
for (let i = 0; i < value.length; i ) {
traverse(value[i], seen)
}
} else if (isSet(value) || isMap(value)) {
value.forEach((v: any) => {
traverse(v, seen)
})
} else if (isPlainObject(value)) {
for (const key in value) {
traverse((value as any)[key], seen)
}
}
return value
}
traverse函数使用递归的方式访问value的子属性,因为deep属于watcher的一个配置选项。 2.cb参数 在回调函数中,会提供最新的 value、旧 value,以及 onCleanup 函数用以清除副作用。
代码语言:javascript复制export type WatchCallback<V = any, OV = any> = (
value: V,
oldValue: OV,
onCleanup: OnCleanup
) => any
3.options的创建
代码语言:javascript复制export interface WatchOptionsBase extends DebuggerOptions {
flush?: 'pre' | 'post' | 'sync'
}
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
immediate?: Immediate
deep?: boolean
}
options 的类型 WatchOptions 继承了 WatchOptionsBase,这也就是 watch 除了 immediate 和 deep 这两个特有的参数外,还可以传递 WatchOptionsBase 中的所有参数以控制副作用执行的行为。 Options 中的 flush 决定了 watcher 的执行时机:
代码语言:javascript复制 if (flush === 'sync') {
scheduler = job as any // the scheduler function gets called directly
} else if (flush === 'post') {
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
// default: 'pre'
job.pre = true
if (instance) job.id = instance.uid
scheduler = () => queueJob(job)
}
当flush为 sync 的时,表示它是一个同步 watcher,即当数据变化时同步执行回调函数。 当flush为 pre的时,回调函数通过 queueJob 的方式在组件更新之前执行。如果组件还没挂载,则同步执行确保回调函数在组件挂载之前执行。 如果没设置flush,回调函数通过 queuePostRenderEffect 的方式在组件更新之后执行
doWatch函数
代码语言:javascript复制if (isRef(source)) {
getter = () => source.value
forceTrigger = isShallow(source)
} else if (isReactive(source)) {
getter = () => source
deep = true
} else if (isArray(source)) {
isMultiSource = true
forceTrigger = source.some(s => isReactive(s) || isShallow(s))
getter = () =>
source.map(s => {
if (isRef(s)) {
return s.value
} else if (isReactive(s)) {
return traverse(s)
} else if (isFunction(s)) {
return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
} else {
__DEV__ && warnInvalidSource(s)
}
})
} else if (isFunction(source)) {
if (cb) {
// getter with cb
getter = () =>
callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
} else {
// no cb -> simple effect
getter = () => {
if (instance && instance.isUnmounted) {
return
}
if (cleanup) {
cleanup()
}
return callWithAsyncErrorHandling(
source,
instance,
ErrorCodes.WATCH_CALLBACK,
[onCleanup]
)
}
}
} else {
getter = NOOP
__DEV__ && warnInvalidSource(source)
}
source标准化根据source的类型划分: 如果source是ref对象,则创建一个访问source.value的getter函数。 如果source是reactive对象,则创建一个访问source的getter函数,并设置deep为true。 如果source是函数,则进一步判断cb是否存在,对于watch Api来说,cb一定存在,且是一个回调函数,getter就是一个简单的对source函数封装的函数。 如果source不满足上述条件,则在非生产环境下报警告,提示source类型不合法。
当deep为true时,会用traverse函数把getter再包装一层。
代码语言:javascript复制if (cb && deep) {
const baseGetter = getter
getter = () => traverse(baseGetter())
}
定义清除副作用函数
代码语言:javascript复制// 清除副作用函数
let cleanup: () => void
let onCleanup: OnCleanup = (fn: () => void) => {
cleanup = effect.onStop = () => {
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
}
}
总结
watch
作用是对传入的某个或多个值的变化进行监听;触发时会返回新值和老值;也就是说第一次不会执行,只有变化时才会重新执行watchEffect
是传入一个立即执行函数,所以默认第一次也会执行一次;不需要传入监听内容,会自动收集函数内的数据源作为依赖,在依赖变化的时候又会重新执行该函数,如果没有依赖就不会执行;而且不会返回变化前后的新值和老值
watch
加Immediate
也可以立即执行