Vue3 watch源码解析
一、watcheffect
实际运行函数dowatch 首先是reactive对象作为function
watchEffect(() => {
dummy = state.count
})
return doWatch(effect, null, options)
判断是否是方法
else if (isFunction(source))
观察有没有回调函数cb按照上面的例子来说并没有所以进入了else,源代码也有注释的simple effect,后面就开始判断组件的状态,
function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
{ immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ,
instance = currentInstance
): WatchStopHandle {
if (__DEV__ && !cb) {
if (immediate !== undefined) {
warn(
`watch() "immediate" option is only respected when using the ` +
`watch(source, callback, options?) signature.`
)
}
if (deep !== undefined) {
warn(
`watch() "deep" option is only respected when using the ` +
`watch(source, callback, options?) signature.`
)
}
}
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.`
)
}
let getter: () => any
let forceTrigger = false
let isMultiSource = false
if (isRef(source)) {//判断是不是ref
getter = () => (source as Ref).value
forceTrigger = !!(source as Ref)._shallow//!!实际上要有意义
} else if (isReactive(source)) {
getter = () => source
deep = true//自动添加深层
} else if (isArray(source)) {
isMultiSource = true
forceTrigger = source.some(isReactive)
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, [
instance && (instance.proxy as any)
])
} else {
__DEV__ && warnInvalidSource(s)
}
})
} else if (isFunction(source)) {
if (cb) {
// getter with cb
getter = () =>
callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER, [
instance && (instance.proxy as any)
])
} else {
// no cb -> simple effect
//getter方法获取数据源的值
getter = () => {
if (instance && instance.isUnmounted) {
return
}
if (cleanup) {
cleanup()
}
return callWithAsyncErrorHandling(
source,
instance,
ErrorCodes.WATCH_CALLBACK,
[onInvalidate]
)
}
}
} else {
getter = NOOP
__DEV__ && warnInvalidSource(source)
}
// 2.x array mutation watch compat
if (__COMPAT__ && cb && !deep) {
const baseGetter = getter
getter = () => {
const val = baseGetter()
if (
isArray(val) &&
checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)
) {
traverse(val)
}
return val
}
}
if (cb && deep) {
const baseGetter = getter
getter = () => traverse(baseGetter())
}
let cleanup: () => void
let onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {
cleanup = runner.options.onStop = () => {
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
}
}
// in SSR there is no need to setup an actual effect, and it should be noop
// unless it's eager
if (__NODE_JS__ && isInSSRComponentSetup) {
// we will also not call the invalidate callback (+ runner is not set up)
onInvalidate = NOOP
if (!cb) {
getter()
} else if (immediate) {
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
getter(),
undefined,
onInvalidate
])
}
return NOOP
}
let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
//调度任务获取新值
const job: SchedulerJob = () => {
if (!runner.active) {
return
}
if (cb) {
// watch(source, cb)
const newValue = runner()
if (
deep ||
forceTrigger ||
(isMultiSource
? (newValue as any[]).some((v, i) =>
hasChanged(v, (oldValue as any[])[i])
)
: hasChanged(newValue, oldValue)) ||
(__COMPAT__ &&
isArray(newValue) &&
isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))
) {
// cleanup before running cb again
if (cleanup) {
cleanup()
}
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
newValue,
// pass undefined as the old value when it's changed for the first time
oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
onInvalidate
])
oldValue = newValue
}
} else {
// watchEffect
runner()
}
}
// important: mark the job as a watcher callback so that scheduler knows
// it is allowed to self-trigger (#1727)
job.allowRecurse = !!cb
let scheduler: ReactiveEffectOptions['scheduler']
if (flush === 'sync') {
scheduler = job
} else if (flush === 'post') {
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
// default: 'pre'
scheduler = () => {
if (!instance || instance.isMounted) {
queuePreFlushCb(job)
} else {
// with 'pre' option, the first call must happen before
// the component is mounted so it is called synchronously.
job()
}
}
}
const runner = effect(getter, {
lazy: true,
onTrack,
onTrigger,
scheduler//触发方法
})
recordInstanceBoundEffect(runner, instance)
// initial run
if (cb) {
if (immediate) {
job()
} else {
oldValue = runner()
}
} else if (flush === 'post') {
queuePostRenderEffect(runner, instance && instance.suspense)
} else {
runner()
}
return () => {
stop(runner)
if (instance) {
remove(instance.effects!, runner)
}
}
}
晚上抽空看了一下computed的实现原理,总来的说封装的很好。。没有watch那么自由
export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>
export function computed<T>(
options: WritableComputedOptions<T>
): WritableComputedRef<T>
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
const c = _computed(getterOrOptions as any)
recordInstanceBoundEffect(c.effect)
return c
}
你可以看见有两个重载,第一个针对只有get的,下一个是get和set的
export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>
export function computed<T>(
options: WritableComputedOptions<T>
): WritableComputedRef<T>
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
setter = __DEV__
? () => {
console.warn('Write operation failed: computed value is readonly')
}
: NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return new ComputedRefImpl(
getter,
setter,
isFunction(getterOrOptions) || !getterOrOptions.set
) as any
}
开始分配set与get方法
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean
) {
this.effect = effect(getter, {
lazy: true,
scheduler: () => {
if (!this._dirty) {
this._dirty = true
trigger(toRaw(this), TriggerOpTypes.SET, 'value')
}
}
})
this[ReactiveFlags.IS_READONLY] = isReadonly
}
进入return中的构造器可以发现lazy是true所以必须要值更新以后才能触发,而不是立即执行的,它也拥有自己的调度器会获取值,toraw将响应式对象转换为普通对象,TriggerOpTypes.set在值set的时候进行触发,更新value
export interface ReactiveEffect<T = any> {
(): T
_isEffect: true//effect的标志属性用于判断
id: number//标识
active: boolean
raw: () => T//原始函数
deps: Array<Dep>
options: ReactiveEffectOptions//配置选项
allowRecurse: boolean
}
export interface ReactiveEffectOptions {
lazy?: boolean//是否立即执行
scheduler?: (job: ReactiveEffect) => void//是否自定义调度
onTrack?: (event: DebuggerEvent) => void//是否收集监听函数
onTrigger?: (event: DebuggerEvent) => void//是否触发监听函数
onStop?: () => void//是否停止监听函数
allowRecurse?: boolean
}
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) {
//设置函数
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()//是否立即执行
}
return effect
}