一、computed(计算属性)
作用:在Vue实例中的data属性发生变化时,自动计算并更新相关属性的值。与methods方法不同,computed是基于响应式依赖进行缓存的,只有相关依赖发生变化时才会重新计算。
源码如下:
import { type DebuggerOptions, ReactiveEffect } from './effect'
import { type Ref, trackRefValue, triggerRefValue } from './ref'
import { NOOP, hasChanged, isFunction } from '@vue/shared'
import { toRaw } from './reactive'
import type { Dep } from './dep'
import { DirtyLevels, ReactiveFlags } from './constants'
import { warn } from './warning'
declare const ComputedRefSymbol: unique symbol
export interface ComputedRef<T = any> extends WritableComputedRef<T> {
readonly value: T
[ComputedRefSymbol]: true
}
export interface WritableComputedRef<T> extends Ref<T> {
readonly effect: ReactiveEffect<T>
}
export type ComputedGetter<T> = (oldValue?: T) => T
export type ComputedSetter<T> = (newValue: T) => void
export interface WritableComputedOptions<T> {
get: ComputedGetter<T>
set: ComputedSetter<T>
}
export const COMPUTED_SIDE_EFFECT_WARN =
`Computed is still dirty after getter evaluation,` +
` likely because a computed is mutating its own dependency in its getter.` +
` State mutations in computed getters should be avoided. ` +
` Check the docs for more details: https://vuejs.org/guide/essentials/computed.html#getters-should-be-side-effect-free`
export class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
public readonly [ReactiveFlags.IS_READONLY]: boolean = false
public _cacheable: boolean
/**
* Dev only
*/
_warnRecursive?: boolean
constructor(
private getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean,
isSSR: boolean,
) {
// 依赖变化了 并且脏值是false
// effect(()=>{依赖函数}) 依赖没变这个不走
this.effect = new ReactiveEffect(
() => getter(this._value),
() =>
triggerRefValue(
this,
this.effect._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect
? DirtyLevels.MaybeDirty_ComputedSideEffect
: DirtyLevels.MaybeDirty,
),
)
this.effect.computed = this
this.effect.active = this._cacheable = !isSSR
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
const self = toRaw(this)
// 默认self.effect.dirty为true
if (
(!self._cacheable || self.effect.dirty) &&
hasChanged(self._value, (self._value = self.effect.run()!))
) {
triggerRefValue(self, DirtyLevels.Dirty)
}
trackRefValue(self)
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty_ComputedSideEffect) {
if (__DEV__ && (__TEST__ || this._warnRecursive)) {
warn(COMPUTED_SIDE_EFFECT_WARN, `\n\ngetter: `, this.getter)
}
triggerRefValue(self, DirtyLevels.MaybeDirty_ComputedSideEffect)
}
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
// #region polyfill _dirty for backward compatibility third party code for Vue <= 3.3.x
get _dirty() {
return this.effect.dirty
}
set _dirty(v) {
this.effect.dirty = v
}
// #endregion
}
/**
* Takes a getter function and returns a readonly reactive ref object for the
* returned value from the getter. It can also take an object with get and set
* functions to create a writable ref object.
*
* @example
* ```js
* // Creating a readonly computed ref:
* const count = ref(1)
* const plusOne = computed(() => count.value + 1)
*
* console.log(plusOne.value) // 2
* plusOne.value++ // error
* ```
*
* ```js
* // Creating a writable computed ref:
* const count = ref(1)
* const plusOne = computed({
* get: () => count.value + 1,
* set: (val) => {
* count.value = val - 1
* }
* })
*
* plusOne.value = 1
* console.log(count.value) // 0
* ```
*
* @param getter - Function that produces the next value.
* @param debugOptions - For debugging. See {@link https://vuejs.org/guide/extras/reactivity-in-depth.html#computed-debugging}.
* @see {@link https://vuejs.org/api/reactivity-core.html#computed}
*/
export function computed<T>(
getter: ComputedGetter<T>,
debugOptions?: DebuggerOptions,
): ComputedRef<T>
export function computed<T>(
options: WritableComputedOptions<T>,
debugOptions?: DebuggerOptions,
): WritableComputedRef<T>
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions,
isSSR = false,
) {
// 格式化参数
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
// 判断是函数式还是选项式,函数式写法 只能支持一个getter函数不允许修改值的
const onlyGetter = isFunction(getterOrOptions)
if (onlyGetter) {
getter = getterOrOptions //传过来的函数赋给getter
setter = __DEV__
? () => {
warn('Write operation failed: computed value is readonly')
}
: NOOP
} else {
getter = getterOrOptions.get //选项式则是直接赋值
setter = getterOrOptions.set
}
const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
if (__DEV__ && debugOptions && !isSSR) {
cRef.effect.onTrack = debugOptions.onTrack
cRef.effect.onTrigger = debugOptions.onTrigger
}
return cRef as any
}
二、watch(侦听器)
源码如下:
import {
type ComputedRef,
type DebuggerOptions,
type EffectScheduler,
ReactiveEffect,
ReactiveFlags,
type Ref,
getCurrentScope,
isReactive,
isRef,
isShallow,
} from '@vue/reactivity'
import { type SchedulerJob, queueJob } from './scheduler'
import {
EMPTY_OBJ,
NOOP,
extend,
hasChanged,
isArray,
isFunction,
isMap,
isObject,
isPlainObject,
isSet,
isString,
remove,
} from '@vue/shared'
import {
type ComponentInternalInstance,
currentInstance,
isInSSRComponentSetup,
setCurrentInstance,
} from './component'
import {
ErrorCodes,
callWithAsyncErrorHandling,
callWithErrorHandling,
} from './errorHandling'
import { queuePostRenderEffect } from './renderer'
import { warn } from './warning'
import { DeprecationTypes } from './compat/compatConfig'
import { checkCompatEnabled, isCompatEnabled } from './compat/compatConfig'
import type { ObjectWatchOptionItem } from './componentOptions'
import { useSSRContext } from './helpers/useSsrContext'
export type WatchEffect = (onCleanup: OnCleanup) => void
export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
export type WatchCallback<V = any, OV = any> = (
value: V,
oldValue: OV,
onCleanup: OnCleanup,
) => any
type MapSources<T, Immediate> = {
[K in keyof T]: T[K] extends WatchSource<infer V>
? Immediate extends true
? V | undefined
: V
: T[K] extends object
? Immediate extends true
? T[K] | undefined
: T[K]
: never
}
type OnCleanup = (cleanupFn: () => void) => void
export interface WatchOptionsBase extends DebuggerOptions {
flush?: 'pre' | 'post' | 'sync'
}
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
immediate?: Immediate
deep?: boolean
once?: boolean
}
export type WatchStopHandle = () => void
// Simple effect.
export function watchEffect(
effect: WatchEffect,
options?: WatchOptionsBase,
): WatchStopHandle {
return doWatch(effect, null, options)
}
export function watchPostEffect(
effect: WatchEffect,
options?: DebuggerOptions,
) {
return doWatch(
effect,
null,
__DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
)
}
export function watchSyncEffect(
effect: WatchEffect,
options?: DebuggerOptions,
) {
return doWatch(
effect,
null,
__DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' },
)
}
// initial value for watchers to trigger on undefined initial values
const INITIAL_WATCHER_VALUE = {}
type MultiWatchSources = (WatchSource<unknown> | object)[]
// overload: single source + cb
export function watch<T, Immediate extends Readonly<boolean> = false>(
source: WatchSource<T>,
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
// overload: array of multiple sources + cb
export function watch<
T extends MultiWatchSources,
Immediate extends Readonly<boolean> = false,
>(
sources: [...T],
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
// overload: multiple sources w/ `as const`
// watch([foo, bar] as const, () => {})
// somehow [...T] breaks when the type is readonly
export function watch<
T extends Readonly<MultiWatchSources>,
Immediate extends Readonly<boolean> = false,
>(
source: T,
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
// overload: watching reactive object w/ cb
export function watch<
T extends object,
Immediate extends Readonly<boolean> = false,
>(
source: T,
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
// implementation
export 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)
}
function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
{
immediate,
deep,
flush,
once,
onTrack,
onTrigger,
}: WatchOptions = EMPTY_OBJ,
): WatchStopHandle {
if (cb && once) {
const _cb = cb
cb = (...args) => {
_cb(...args)
unwatch()
}
}
// TODO remove in 3.5
if (__DEV__ && deep !== void 0 && typeof deep === 'number') {
warn(
`watch() "deep" option with number value will be used as watch depth in future versions. ` +
`Please use a boolean instead to avoid potential breakage.`,
)
}
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.`,
)
}
if (once !== undefined) {
warn(
`watch() "once" 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.`,
)
}
const instance = currentInstance
const reactiveGetter = (source: object) =>
deep === true
? source // traverse will happen in wrapped getter below
: // for deep: false, only traverse root-level properties
traverse(source, deep === false ? 1 : undefined)
let getter: () => any
let forceTrigger = false
let isMultiSource = false
if (isRef(source)) {
getter = () => source.value
forceTrigger = isShallow(source)
} else if (isReactive(source)) {
getter = () => reactiveGetter(source)
forceTrigger = 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 reactiveGetter(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 (cleanup) {
cleanup()
}
return callWithAsyncErrorHandling(
source,
instance,
ErrorCodes.WATCH_CALLBACK,
[onCleanup],
)
}
}
} 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) | undefined
let onCleanup: OnCleanup = (fn: () => void) => {
cleanup = effect.onStop = () => {
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
cleanup = effect.onStop = undefined
}
}
// in SSR there is no need to setup an actual effect, and it should be noop
// unless it's eager or sync flush
let ssrCleanup: (() => void)[] | undefined
if (__SSR__ && isInSSRComponentSetup) {
// we will also not call the invalidate callback (+ runner is not set up)
onCleanup = NOOP
if (!cb) {
getter()
} else if (immediate) {
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
getter(),
isMultiSource ? [] : undefined,
onCleanup,
])
}
if (flush === 'sync') {
const ctx = useSSRContext()!
ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
} else {
return NOOP
}
}
let oldValue: any = isMultiSource
? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
: INITIAL_WATCHER_VALUE
const job: SchedulerJob = () => {
if (!effect.active || !effect.dirty) {
return
}
if (cb) {
// watch(source, cb)
const newValue = effect.run()
if (
deep ||
forceTrigger ||
(isMultiSource
? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[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
// 第一次执行是undefined或者空数组
oldValue === INITIAL_WATCHER_VALUE
? undefined
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
? []
: oldValue,
onCleanup,
])
// 直接赋值如果是对象就直接引用了所以新值和旧值是一样的
oldValue = newValue
}
} else {
// watchEffect
effect.run()
}
}
// important: mark the job as a watcher callback so that scheduler knows
// it is allowed to self-trigger (#1727)
job.allowRecurse = !!cb
let scheduler: EffectScheduler
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)
}
const effect = new ReactiveEffect(getter, NOOP, scheduler)
const scope = getCurrentScope()
const unwatch = () => {
effect.stop()
if (scope) {
remove(scope.effects, effect)
}
}
if (__DEV__) {
effect.onTrack = onTrack
effect.onTrigger = onTrigger
}
// initial run
if (cb) {
if (immediate) {
job()
} else {
oldValue = effect.run()
}
} else if (flush === 'post') {
queuePostRenderEffect(
effect.run.bind(effect),
instance && instance.suspense,
)
} else {
effect.run()
}
if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
return unwatch
}
// this.$watch
export function instanceWatch(
this: ComponentInternalInstance,
source: string | Function,
value: WatchCallback | ObjectWatchOptionItem,
options?: WatchOptions,
): WatchStopHandle {
const publicThis = this.proxy as any
const getter = isString(source)
? source.includes('.')
? createPathGetter(publicThis, source)
: () => publicThis[source]
: source.bind(publicThis, publicThis)
let cb
if (isFunction(value)) {
cb = value
} else {
cb = value.handler as Function
options = value
}
const reset = setCurrentInstance(this)
const res = doWatch(getter, cb.bind(publicThis), options)
reset()
return res
}
export function createPathGetter(ctx: any, path: string) {
const segments = path.split('.')
return () => {
let cur = ctx
for (let i = 0; i < segments.length && cur; i++) {
cur = cur[segments[i]]
}
return cur
}
}
export function traverse(
value: unknown,
depth = Infinity,
seen?: Set<unknown>,
) {
if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
return value
}
seen = seen || new Set()
if (seen.has(value)) {
return value
}
seen.add(value)
depth--
if (isRef(value)) {
traverse(value.value, depth, seen)
} else if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
traverse(value[i], depth, seen)
}
} else if (isSet(value) || isMap(value)) {
value.forEach((v: any) => {
traverse(v, depth, seen)
})
} else if (isPlainObject(value)) {
for (const key in value) {
traverse(value[key], depth, seen)
}
}
return value
}
代码如下(示例):
import {
type ComputedRef,
type DebuggerOptions,
type EffectScheduler,
ReactiveEffect,
ReactiveFlags,
type Ref,
getCurrentScope,
isReactive,
isRef,
isShallow,
} from '@vue/reactivity'
import { type SchedulerJob, queueJob } from './scheduler'
import {
EMPTY_OBJ,
NOOP,
extend,
hasChanged,
isArray,
isFunction,
isMap,
isObject,
isPlainObject,
isSet,
isString,
remove,
} from '@vue/shared'
import {
type ComponentInternalInstance,
currentInstance,
isInSSRComponentSetup,
setCurrentInstance,
} from './component'
import {
ErrorCodes,
callWithAsyncErrorHandling,
callWithErrorHandling,
} from './errorHandling'
import { queuePostRenderEffect } from './renderer'
import { warn } from './warning'
import { DeprecationTypes } from './compat/compatConfig'
import { checkCompatEnabled, isCompatEnabled } from './compat/compatConfig'
import type { ObjectWatchOptionItem } from './componentOptions'
import { useSSRContext } from './helpers/useSsrContext'
export type WatchEffect = (onCleanup: OnCleanup) => void
export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
export type WatchCallback<V = any, OV = any> = (
value: V,
oldValue: OV,
onCleanup: OnCleanup,
) => any
type MapSources<T, Immediate> = {
[K in keyof T]: T[K] extends WatchSource<infer V>
? Immediate extends true
? V | undefined
: V
: T[K] extends object
? Immediate extends true
? T[K] | undefined
: T[K]
: never
}
type OnCleanup = (cleanupFn: () => void) => void
export interface WatchOptionsBase extends DebuggerOptions {
flush?: 'pre' | 'post' | 'sync'
}
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
immediate?: Immediate
deep?: boolean
once?: boolean
}
export type WatchStopHandle = () => void
// Simple effect.
export function watchEffect(
effect: WatchEffect,
options?: WatchOptionsBase,
): WatchStopHandle {
return doWatch(effect, null, options)
}
export function watchPostEffect(
effect: WatchEffect,
options?: DebuggerOptions,
) {
return doWatch(
effect,
null,
__DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
)
}
export function watchSyncEffect(
effect: WatchEffect,
options?: DebuggerOptions,
) {
return doWatch(
effect,
null,
__DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' },
)
}
// initial value for watchers to trigger on undefined initial values
const INITIAL_WATCHER_VALUE = {}
type MultiWatchSources = (WatchSource<unknown> | object)[]
// overload: single source + cb
export function watch<T, Immediate extends Readonly<boolean> = false>(
source: WatchSource<T>,
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
// overload: array of multiple sources + cb
export function watch<
T extends MultiWatchSources,
Immediate extends Readonly<boolean> = false,
>(
sources: [...T],
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
// overload: multiple sources w/ `as const`
// watch([foo, bar] as const, () => {})
// somehow [...T] breaks when the type is readonly
export function watch<
T extends Readonly<MultiWatchSources>,
Immediate extends Readonly<boolean> = false,
>(
source: T,
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
// overload: watching reactive object w/ cb
export function watch<
T extends object,
Immediate extends Readonly<boolean> = false,
>(
source: T,
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
// implementation
export 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)
}
function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
{
immediate,
deep,
flush,
once,
onTrack,
onTrigger,
}: WatchOptions = EMPTY_OBJ,
): WatchStopHandle {
if (cb && once) {
const _cb = cb
cb = (...args) => {
_cb(...args)
unwatch()
}
}
// TODO remove in 3.5
if (__DEV__ && deep !== void 0 && typeof deep === 'number') {
warn(
`watch() "deep" option with number value will be used as watch depth in future versions. ` +
`Please use a boolean instead to avoid potential breakage.`,
)
}
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.`,
)
}
if (once !== undefined) {
warn(
`watch() "once" 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.`,
)
}
const instance = currentInstance
const reactiveGetter = (source: object) =>
deep === true
? source // traverse will happen in wrapped getter below
: // for deep: false, only traverse root-level properties
traverse(source, deep === false ? 1 : undefined)
let getter: () => any
let forceTrigger = false
let isMultiSource = false
if (isRef(source)) {
getter = () => source.value
forceTrigger = isShallow(source)
} else if (isReactive(source)) {
getter = () => reactiveGetter(source)
forceTrigger = 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 reactiveGetter(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 (cleanup) {
cleanup()
}
return callWithAsyncErrorHandling(
source,
instance,
ErrorCodes.WATCH_CALLBACK,
[onCleanup],
)
}
}
} 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) | undefined
let onCleanup: OnCleanup = (fn: () => void) => {
cleanup = effect.onStop = () => {
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
cleanup = effect.onStop = undefined
}
}
// in SSR there is no need to setup an actual effect, and it should be noop
// unless it's eager or sync flush
let ssrCleanup: (() => void)[] | undefined
if (__SSR__ && isInSSRComponentSetup) {
// we will also not call the invalidate callback (+ runner is not set up)
onCleanup = NOOP
if (!cb) {
getter()
} else if (immediate) {
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
getter(),
isMultiSource ? [] : undefined,
onCleanup,
])
}
if (flush === 'sync') {
const ctx = useSSRContext()!
ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
} else {
return NOOP
}
}
let oldValue: any = isMultiSource
? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
: INITIAL_WATCHER_VALUE
const job: SchedulerJob = () => {
if (!effect.active || !effect.dirty) {
return
}
if (cb) {
// watch(source, cb)
const newValue = effect.run()
if (
deep ||
forceTrigger ||
(isMultiSource
? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[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
// 第一次执行是undefined或者空数组
oldValue === INITIAL_WATCHER_VALUE
? undefined
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
? []
: oldValue,
onCleanup,
])
// 直接赋值如果是对象就直接引用了所以新值和旧值是一样的
oldValue = newValue
}
} else {
// watchEffect
effect.run()
}
}
// important: mark the job as a watcher callback so that scheduler knows
// it is allowed to self-trigger (#1727)
job.allowRecurse = !!cb
let scheduler: EffectScheduler
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)
}
const effect = new ReactiveEffect(getter, NOOP, scheduler)
const scope = getCurrentScope()
const unwatch = () => {
effect.stop()
if (scope) {
remove(scope.effects, effect)
}
}
if (__DEV__) {
effect.onTrack = onTrack
effect.onTrigger = onTrigger
}
// initial run
if (cb) {
if (immediate) {
job()
} else {
oldValue = effect.run()
}
} else if (flush === 'post') {
queuePostRenderEffect(
effect.run.bind(effect),
instance && instance.suspense,
)
} else {
effect.run()
}
if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
return unwatch
}
// this.$watch
export function instanceWatch(
this: ComponentInternalInstance,
source: string | Function,
value: WatchCallback | ObjectWatchOptionItem,
options?: WatchOptions,
): WatchStopHandle {
const publicThis = this.proxy as any
const getter = isString(source)
? source.includes('.')
? createPathGetter(publicThis, source)
: () => publicThis[source]
: source.bind(publicThis, publicThis)
let cb
if (isFunction(value)) {
cb = value
} else {
cb = value.handler as Function
options = value
}
const reset = setCurrentInstance(this)
const res = doWatch(getter, cb.bind(publicThis), options)
reset()
return res
}
export function createPathGetter(ctx: any, path: string) {
const segments = path.split('.')
return () => {
let cur = ctx
for (let i = 0; i < segments.length && cur; i++) {
cur = cur[segments[i]]
}
return cur
}
}
export function traverse(
value: unknown,
depth = Infinity,
seen?: Set<unknown>,
) {
if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
return value
}
seen = seen || new Set()
if (seen.has(value)) {
return value
}
seen.add(value)
depth--
if (isRef(value)) {
traverse(value.value, depth, seen)
} else if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
traverse(value[i], depth, seen)
}
} else if (isSet(value) || isMap(value)) {
value.forEach((v: any) => {
traverse(v, depth, seen)
})
} else if (isPlainObject(value)) {
for (const key in value) {
traverse(value[key], depth, seen)
}
}
return value
}
总结
computed与watch的区别:
1、computed支持缓存。而watch不支持
2、computed不支持异步,当 computed 内有异步操作时无效,无法监听数据的变化。而watch支持异步监听
3、computed中的函数必须要用return返回,watch中的函数不是必须要用return。
4、computed中如果属性值是函数,默认使用get方法,函数的返回值就是属性的属性值。还有一个set方法,当数据变化时就会调用set方法。watch接受两个参数,第一个是最新的值,第二个是变化之前的值。
5、watch用于监听特定数据的变化并执行副作用操作,而computed用于计算衍生值并返回结果。
computed与watch的使用场景:
watch的使用场景:一个数据影响多个数据,需要在数据变化时执行异步操作或者开销较大的操作时使用。eg:数据搜索
computed:一个数据受多个数据影响,处理复杂的逻辑或多个属性影响一个属性的变化时使用。eg:购物车商品结算