vue3-yuanma/packages/reactivity/src/computed.ts

140 lines
3.6 KiB
TypeScript
Raw Normal View History

import { ReactiveEffect, triggerEffects } from './effect'
import { Ref, trackRefValue, triggerRefValue } from './ref'
import { isFunction, NOOP } from '@vue/shared'
import { ReactiveFlags, toRaw } from './reactive'
2021-07-08 02:37:28 +08:00
import { Dep } from './dep'
import { TriggerOpTypes } from '@vue/runtime-core'
2018-09-19 23:35:38 +08:00
export interface ComputedRef<T = any> extends WritableComputedRef<T> {
readonly value: T
2018-09-19 23:35:38 +08:00
}
2019-10-05 22:10:37 +08:00
export interface WritableComputedRef<T> extends Ref<T> {
readonly effect: ReactiveEffect<T>
}
export type ComputedGetter<T> = (ctx?: any) => T
2019-10-22 01:57:20 +08:00
export type ComputedSetter<T> = (v: T) => void
export interface WritableComputedOptions<T> {
2019-10-22 01:57:20 +08:00
get: ComputedGetter<T>
set: ComputedSetter<T>
}
type ComputedScheduler = (fn: () => void) => void
let scheduler: ComputedScheduler | undefined
/**
* Set a scheduler for deferring computed computations
*/
export const setComputedScheduler = (s: ComputedScheduler | undefined) => {
scheduler = s
}
class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
private _dirty = true
private _changed = false
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true;
public readonly [ReactiveFlags.IS_READONLY]: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean
) {
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
if (scheduler) {
if (this.dep) {
const effects: ReactiveEffect[] = []
scheduler(() => {
if ((this._get(), this._changed)) {
if (__DEV__) {
triggerEffects(effects, {
target: this,
type: TriggerOpTypes.SET,
key: 'value',
newValue: this._value
})
} else {
triggerEffects(effects)
}
}
})
// chained upstream computeds are notified synchronously to ensure
// value invalidation in case of sync access; normal effects are
// deferred to be triggered in scheduler.
for (const e of this.dep) {
if (e.computed) {
e.scheduler!()
} else {
effects.push(e)
}
}
}
} else {
triggerRefValue(this)
}
}
})
this.effect.computed = true
this[ReactiveFlags.IS_READONLY] = isReadonly
}
private _get() {
if (this._dirty) {
const oldValue = this._value
this._changed = oldValue !== (this._value = this.effect.run()!)
this._dirty = false
}
}
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
const self = toRaw(this)
self._get()
trackRefValue(self)
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
2019-10-22 01:57:20 +08:00
export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>
2019-06-06 12:35:49 +08:00
export function computed<T>(
options: WritableComputedOptions<T>
): WritableComputedRef<T>
export function computed<T>(
2019-10-22 01:57:20 +08:00
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
2019-10-22 23:26:48 +08:00
) {
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
2018-09-19 23:35:38 +08:00
}