computed
接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。
// 只读
function computed<T>(
getter: () => T,
// 参见下面的"Computed 调试"链接
debuggerOptions?: DebuggerOptions,
): Readonly<Ref<Readonly<T>>>
// 可写
function computed<T>(
options: {
get: () => T
set: (value: T) => void
},
debuggerOptions?: DebuggerOptions,
): Ref<T>
computed 有两个签名,可读和可写。
支持get
import type { Dep } from './dep'
import { ReactiveEffect } from './effect'
import { type Ref, trackRefValue, triggerRefValue } from './ref'
declare const ComputedRefSymbol: unique symbol
export interface ComputedRef<T = any> extends Ref {
readonly value: T
[ComputedRefSymbol]: true
}
type ComputedGetter<T> = (...args: any[]) => T
export class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
public _dirty = true
constructor(getter: ComputedGetter<T>) {
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
})
}
get value() {
trackRefValue(this)
if (this._dirty) {
this._dirty = false
this._value = this.effect.run()
}
return this._value
}
set value(_newValue: T) {}
}
export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T> {
return new ComputedRefImpl(getter) as any
}
当组件中读取计算属性时,会触发 get value(),调用trackRefValue收集依赖,_dirty
属性用来标记属性是否是脏值(脏值不需要重新计算),第一次访问时_dirty
肯定是 false,于是就会触发this.effect.run()
,而在 constructor中,this.effect
是 ReactiveEffect 传入getter(computed 传入的函数)以及:
// 我是scheduler
() => {
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
}
export class ReactiveEffect<T = any> {
// fn 指 updateComponent()
constructor(public fn: () => T, public scheduler?: EffectScheduler | null) {}
run() {
let parent: ReactiveEffect | undefined = activeEffect
activeEffect = this
const res = this.fn()
activeEffect = parent
return res
}
}
调用this.effect.run
时,activeEffect 为 this.effect,那么执行getter时,其中如果有响应式数据,就会将 activeEffect 收集到 Dep 中,每当其中的响应式数据更新时,会触发set,set 会遍历 dep(其中就包括这个的 computed 的 effect)并执行他的scheduler(ReactiveEffect的第二个函数)。scheduler 中如果_dirty
是false(已经有值且标记为不需要更新),那么就会设置_dirty
为 true,并执行triggerRefValue。对应的组件 effect 会被触发,就会重新访问get value(),这时就会触发重新计算。
支持 set
import { isFunction } from '../shared'
import type { Dep } from './dep'
import { ReactiveEffect } from './effect'
import { type Ref, trackRefValue, triggerRefValue } from './ref'
declare const ComputedRefSymbol: unique symbol
export interface ComputedRef<T = any> extends Ref {
readonly value: T
[ComputedRefSymbol]: true
}
export type ComputedGetter<T> = (...args: any[]) => T
export type ComputedSetter<T> = (v: T) => void
export interface WritableComputedOptions<T> {
get: ComputedGetter<T>
set: ComputedSetter<T>
}
export class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
public _dirty = true
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
) {
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
})
}
get value() {
trackRefValue(this)
if (this._dirty) {
this._dirty = false
this._value = this.effect.run()
}
return this._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
export function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>
export function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
const onlyGetter = isFunction(getterOrOptions)
if (onlyGetter) {
getter = getterOrOptions
setter = () => {}
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return new ComputedRefImpl(getter, setter) as any
}
watch
侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。
// 侦听单个来源
function watch<T>(
source: WatchSource<T>,
callback: WatchCallback<T>,
options?: WatchOptions
): WatchHandle
// 侦听多个来源
function watch<T>(
sources: WatchSource<T>[],
callback: WatchCallback<T[]>,
options?: WatchOptions
): WatchHandle
type WatchCallback<T> = (
value: T,
oldValue: T,
onCleanup: (cleanupFn: () => void) => void
) => void
type WatchSource<T> =
| Ref<T> // ref
| (() => T) // getter
| (T extends object ? T : never) // 响应式对象
interface WatchOptions extends WatchEffectOptions {
immediate?: boolean // 默认:false
deep?: boolean | number // 默认:false
flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
once?: boolean // 默认:false (3.4+)
}
interface WatchHandle {
(): void // 可调用,与 `stop` 相同
pause: () => void
resume: () => void
stop: () => void
}
使用 ReactiveEffect 就可以快速实现
import { ReactiveEffect } from "../reactivity"
export type WatchEffect = (onCleanup: OnCleanup) => void
export type WatchSource<T = any> = () => T
type OnCleanup = (cleanupFn: () => void) => void
export function watch<T>(
source: WatchSource<T>,
cb: (newValue: T, oldValue: T) => void,
) {
let oldValue: T
const effect = new ReactiveEffect(() => {
const newValue = source()
cb(newValue, oldValue)
oldValue = newValue
}, () => {
const newValue = source()
cb(newValue, oldValue)
oldValue = newValue
})
effect.run()
}
优化:
export function watch<T>(
source: WatchSource<T>,
cb: (newValue: T, oldValue: T) => void,
) {
const getter = () => source()
let oldValue = getter()
const job = () => {
const newValue = getter()
if (hasChanged(newValue, oldValue)) {
cb(newValue, oldValue)
oldValue = newValue
}
}
const effect = new ReactiveEffect(getter, job)
effect.run()
}
然后在此基础上继续拓展,支持 Ref:
export type WatchSource<T = any> = () => T | Ref<T>
type OnCleanup = (cleanupFn: () => void) => void
export function watch<T>(
source: WatchSource<T>,
cb: (newValue: T, oldValue: T) => void,
) {
const getter = () => {
const value = source()
return isRef(value) ? value.value : value
}
let oldValue = getter()
const job = () => {
const newValue = getter()
if (hasChanged(newValue, oldValue)) {
cb(newValue, oldValue)
oldValue = newValue
}
}
const effect = new ReactiveEffect(getter, job)
effect.run()
}
支持数组类型的 source
export function watch<T>(
source: WatchSource<T> | WatchSource<T>[],
cb: (newValue: T | T[], oldValue: T | T[]) => void,
) {
let getter: () => any
let isMultiSource = false
if (isFunction(source)) {
getter = source as () => any
} else if (isRef(source)) {
getter = () => source.value
} else if (Array.isArray(source)) {
isMultiSource = true
getter = () => {
return source.map(s => {
if (isRef(s)) {
return s.value
}
return s()
})
}
} else {
getter = () => source
}
let oldValue = getter()
const job = () => {
const newValue = effect.run()
if (isMultiSource
? (newValue as any[]).some((v, i) =>
hasChanged(v, (oldValue as T[])?.[i]),
)
: hasChanged(newValue, oldValue)) {
cb(newValue, oldValue)
oldValue = newValue
}
}
const effect = new ReactiveEffect(getter, job)
effect.run()
}
支持 immediate
export type WatchSource<T = any> = Ref<T> | (() => T)
type OnCleanup = (cleanupFn: () => void) => void
interface WatchOptions {
immediate?: boolean
deep?: boolean | number
}
export function watch<T>(
source: WatchSource<T> | WatchSource<T>[],
cb: (newValue: T | T[], oldValue: T | T[]) => void,
options?: WatchOptions,
) {
const { immediate = false } = options || {}
let getter: () => any
let isMultiSource = false
if (isFunction(source)) {
getter = source as () => any
} else if (isRef(source)) {
getter = () => source.value
} else if (Array.isArray(source)) {
isMultiSource = true
getter = () => {
return source.map(s => {
if (isRef(s)) {
return s.value
}
return s()
})
}
} else {
getter = () => source
}
const job = () => {
const newValue = effect.run()
if (isMultiSource
? (newValue as any[]).some((v, i) =>
hasChanged(v, (oldValue as T[])?.[i]),
)
: hasChanged(newValue, oldValue)) {
cb(newValue, oldValue)
oldValue = newValue
}
}
const effect = new ReactiveEffect(getter, job)
let oldValue: any
if (immediate) {
// job中会调用一次effect.run拿值,所以不用再次调用了
job()
} else {
oldValue = effect.run()
}
}
支持 deep
export function watch<T>(
source: WatchSource<T> | WatchSource<T>[],
cb: (newValue: T | T[], oldValue: T | T[]) => void,
options?: WatchOptions,
) {
const { immediate = false, deep = false } = options || {}
let getter: () => any
let isMultiSource = false
if (isFunction(source)) {
getter = source as () => any
} else if (isRef(source)) {
getter = () => source.value
} else if (Array.isArray(source)) {
isMultiSource = true
getter = () => {
return source.map(s => {
if (isRef(s)) {
return s.value
}
return s()
})
}
} else {
getter = () => source
}
if (deep) {
const baseGetter = getter
getter = () => traverse(baseGetter())
}
const job = () => {
const newValue = effect.run()
if (isMultiSource
? (newValue as any[]).some((v, i) =>
hasChanged(v, (oldValue as T[])?.[i]),
)
: hasChanged(newValue, oldValue)) {
cb(newValue, oldValue)
oldValue = newValue
}
}
const effect = new ReactiveEffect(getter, job)
let oldValue: any
if (immediate) {
job()
} else {
oldValue = effect.run()
}
}
export function traverse(value: unknown, seen?: Set<unknown>) {
if (!isObject(value)) return value
seen = seen || new Set()
if (seen.has(value)) {
return value
}
seen.add(value)
if (isRef(value)) {
traverse(value.value, seen)
} else if (Array.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[key], seen)
}
}
return value
}
watchEffect
立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
export function watch<T>(
source: WatchSource<T> | WatchSource<T>[],
cb: (newValue: T | T[], oldValue: T | T[]) => void,
options?: WatchOptions
) {
doWatch(source, cb, options);
}
export function watchEffect(source: WatchEffect) {
doWatch(source, null)
}
function doWatch<T>(
source: WatchSource<T> | WatchSource<T>[] | WatchEffect,
cb: null | ((newValue: T | T[], oldValue: T | T[]) => void),
options?: WatchOptions
) {
const { immediate = false, deep = false } = options || {};
let getter: () => any;
let isMultiSource = false;
if (isFunction(source)) {
getter = source as () => any;
} else if (isRef(source)) {
getter = () => source.value;
} else if (Array.isArray(source)) {
isMultiSource = true;
getter = () => {
return source.map((s) => {
if (isRef(s)) {
return s.value;
}
return s();
});
};
} else {
getter = () => source;
}
if (deep) {
const baseGetter = getter;
getter = () => traverse(baseGetter());
}
const job = () => {
const newValue = effect.run();
if (
isMultiSource
? (newValue as any[]).some((v, i) =>
hasChanged(v, (oldValue as T[])?.[i])
)
: hasChanged(newValue, oldValue)
) {
cb?.(newValue, oldValue);
oldValue = newValue;
}
};
const effect = new ReactiveEffect(getter, job);
let oldValue: any;
if (immediate) {
job();
} else {
oldValue = effect.run();
}
}
支持 onCleanup
首先,拓展 ReactiveEffect,使其支持 stop,然后watch 的 callback 增加onCleanup参数,他接受一个函数,并将其保存起来,他会在 stop 执行时(也就是 watch return 出的函数被执行时)或者source 变化时,下一次执行 callback 之前触发:
export function watch<T>(
source: WatchSource<T> | WatchSource<T>[],
cb: (newValue: T | T[], oldValue: T | T[], cleanup: OnCleanup) => void,
options?: WatchOptions
) {
return doWatch(source, cb, options);
}
export function watchEffect(source: WatchEffect) {
return doWatch(source, null)
}
function doWatch<T>(
source: WatchSource<T> | WatchSource<T>[] | WatchEffect,
cb: null | ((newValue: T | T[], oldValue: T | T[], cleanup: OnCleanup) => void),
options?: WatchOptions
) {
const { immediate = false, deep = false } = options || {};
let getter: () => any;
let isMultiSource = false;
if (isFunction(source)) {
getter = source as () => any;
} else if (isRef(source)) {
getter = () => source.value;
} else if (Array.isArray(source)) {
isMultiSource = true;
getter = () => {
return source.map((s) => {
if (isRef(s)) {
return s.value;
}
return s();
});
};
} else {
getter = () => source;
}
if (deep) {
const baseGetter = getter;
getter = () => traverse(baseGetter());
}
let cleanup: () => void
let onCleanup: OnCleanup = (fn: () => void) => {
cleanup = effect.onStop = () => {
fn()
}
}
const job = () => {
const newValue = effect.run();
if (
isMultiSource
? (newValue as any[]).some((v, i) =>
hasChanged(v, (oldValue as T[])?.[i])
)
: hasChanged(newValue, oldValue)
) {
if (cleanup) {
cleanup()
}
cb?.(newValue, oldValue, onCleanup);
oldValue = newValue;
}
};
const effect = new ReactiveEffect(getter, job);
let oldValue: any;
if (immediate) {
job();
} else {
oldValue = effect.run();
}
const unwatch = () => {
effect.stop()
}
return unwatch
}