diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 32740f983b1..7f2c5b50d80 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -2,7 +2,7 @@ import { effect, ReactiveEffect, trigger, track } from './effect' import { TriggerOpTypes, TrackOpTypes } from './operations' import { Ref } from './ref' import { isFunction, NOOP } from '@vue/shared' -import { ReactiveFlags } from './reactive' +import { ReactiveFlags, toRaw } from './reactive' export interface ComputedRef extends WritableComputedRef { readonly value: T @@ -20,6 +20,47 @@ export interface WritableComputedOptions { set: ComputedSetter } +class ComputedRefImpl { + private _value!: T + private _dirty = true + + public readonly effect: ReactiveEffect + + public readonly __v_isRef = true; + public readonly [ReactiveFlags.IS_READONLY]: boolean + + constructor( + getter: ComputedGetter, + private readonly _setter: ComputedSetter, + 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 + } + + get value() { + if (this._dirty) { + this._value = this.effect() + this._dirty = false + } + track(toRaw(this), TrackOpTypes.GET, 'value') + return this._value + } + + set value(newValue: T) { + this._setter(newValue) + } +} + export function computed(getter: ComputedGetter): ComputedRef export function computed( options: WritableComputedOptions @@ -42,37 +83,9 @@ export function computed( setter = getterOrOptions.set } - let dirty = true - let value: T - let computed: ComputedRef - - const runner = effect(getter, { - lazy: true, - scheduler: () => { - if (!dirty) { - dirty = true - trigger(computed, TriggerOpTypes.SET, 'value') - } - } - }) - computed = { - __v_isRef: true, - [ReactiveFlags.IS_READONLY]: - isFunction(getterOrOptions) || !getterOrOptions.set, - - // expose effect so computed can be stopped - effect: runner, - get value() { - if (dirty) { - value = runner() - dirty = false - } - track(computed, TrackOpTypes.GET, 'value') - return value - }, - set value(newValue: T) { - setter(newValue) - } - } as any - return computed + return new ComputedRefImpl( + getter, + setter, + isFunction(getterOrOptions) || !getterOrOptions.set + ) as any } diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 83bef572231..64d9f1073ae 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -23,7 +23,7 @@ const convert = (val: T): T => export function isRef(r: Ref | unknown): r is Ref export function isRef(r: any): r is Ref { - return r ? r.__v_isRef === true : false + return Boolean(r && r.__v_isRef === true) } export function ref( @@ -44,26 +44,34 @@ export function shallowRef(value?: unknown) { return createRef(value, true) } +class RefImpl { + private _value: T + + public readonly __v_isRef = true + + constructor(private _rawValue: T, private readonly _shallow = false) { + this._value = _shallow ? _rawValue : convert(_rawValue) + } + + get value() { + track(toRaw(this), TrackOpTypes.GET, 'value') + return this._value + } + + set value(newVal) { + if (hasChanged(toRaw(newVal), this._rawValue)) { + this._rawValue = newVal + this._value = this._shallow ? newVal : convert(newVal) + trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal) + } + } +} + function createRef(rawValue: unknown, shallow = false) { if (isRef(rawValue)) { return rawValue } - let value = shallow ? rawValue : convert(rawValue) - const r = { - __v_isRef: true, - get value() { - track(r, TrackOpTypes.GET, 'value') - return value - }, - set value(newVal) { - if (hasChanged(toRaw(newVal), rawValue)) { - rawValue = newVal - value = shallow ? newVal : convert(newVal) - trigger(r, TriggerOpTypes.SET, 'value', newVal) - } - } - } - return r + return new RefImpl(rawValue, shallow) } export function triggerRef(ref: Ref) { @@ -103,21 +111,32 @@ export type CustomRefFactory = ( set: (value: T) => void } -export function customRef(factory: CustomRefFactory): Ref { - const { get, set } = factory( - () => track(r, TrackOpTypes.GET, 'value'), - () => trigger(r, TriggerOpTypes.SET, 'value') - ) - const r = { - __v_isRef: true, - get value() { - return get() - }, - set value(v) { - set(v) - } +class CustomRefImpl { + private readonly _get: ReturnType>['get'] + private readonly _set: ReturnType>['set'] + + public readonly __v_isRef = true + + constructor(factory: CustomRefFactory) { + const { get, set } = factory( + () => track(this, TrackOpTypes.GET, 'value'), + () => trigger(this, TriggerOpTypes.SET, 'value') + ) + this._get = get + this._set = set + } + + get value() { + return this._get() + } + + set value(newVal) { + this._set(newVal) } - return r as any +} + +export function customRef(factory: CustomRefFactory): Ref { + return new CustomRefImpl(factory) as any } export function toRefs(object: T): ToRefs { @@ -131,19 +150,25 @@ export function toRefs(object: T): ToRefs { return ret } +class ObjectRefImpl { + public readonly __v_isRef = true + + constructor(private readonly _object: T, private readonly _key: K) {} + + get value() { + return this._object[this._key] + } + + set value(newVal) { + this._object[this._key] = newVal + } +} + export function toRef( object: T, key: K ): Ref { - return { - __v_isRef: true, - get value(): any { - return object[key] - }, - set value(newVal) { - object[key] = newVal - } - } as any + return new ObjectRefImpl(object, key) as any } // corner case when use narrows type