From 650889e125078f21168ac62675242f310b1d1c38 Mon Sep 17 00:00:00 2001 From: Robbin Baauw Date: Sat, 15 Aug 2020 10:09:23 +0200 Subject: [PATCH 1/5] perf: refs as classes --- packages/reactivity/src/ref.ts | 115 ++++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 39 deletions(-) diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 83bef572231..cf0eb7c234b 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -44,26 +44,38 @@ export function shallowRef(value?: unknown) { return createRef(value, true) } +class _Ref implements Ref { + private _value: T + + constructor(private _rawValue: T, private _shallow = false) { + this._value = _shallow ? _rawValue : convert(_rawValue) + } + + get value() { + track(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(this, TriggerOpTypes.SET, 'value', newVal) + } + } + + [RefSymbol]: true + + get __v_isRef() { + return true + } +} + 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 _Ref(rawValue, shallow) } export function triggerRef(ref: Ref) { @@ -103,21 +115,36 @@ 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 _CustomRef implements Ref { + private readonly _get: ReturnType>['get'] + private readonly _set: ReturnType>['set'] + + 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) + } + + [RefSymbol]: true + + get __v_isRef() { + return true } - return r as any +} + +export function customRef(factory: CustomRefFactory): Ref { + return new _CustomRef(factory) } export function toRefs(object: T): ToRefs { @@ -131,19 +158,29 @@ export function toRefs(object: T): ToRefs { return ret } +class _ObjectRef implements Ref { + constructor(private _object: T, private _key: K) {} + + get value() { + return this._object[this._key] + } + + set value(newVal) { + this._object[this._key] = newVal + } + + [RefSymbol]: true + + get __v_isRef() { + return true + } +} + 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 _ObjectRef(object, key) } // corner case when use narrows type From e2be33e23d412c564885db6947470dca882f50a1 Mon Sep 17 00:00:00 2001 From: Robbin Baauw Date: Sat, 15 Aug 2020 10:47:19 +0200 Subject: [PATCH 2/5] perf: computed as classes --- packages/reactivity/src/computed.ts | 86 +++++++++++++++++------------ packages/reactivity/src/ref.ts | 6 +- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 32740f983b1..3642139d129 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,6 +1,6 @@ import { effect, ReactiveEffect, trigger, track } from './effect' import { TriggerOpTypes, TrackOpTypes } from './operations' -import { Ref } from './ref' +import { Ref, RefSymbol } from './ref' import { isFunction, NOOP } from '@vue/shared' import { ReactiveFlags } from './reactive' @@ -20,6 +20,52 @@ export interface WritableComputedOptions { set: ComputedSetter } +class _ComputedRef implements ComputedRef { + private _value!: T + private _dirty = true + + public readonly effect: ReactiveEffect; + + public [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(this, TriggerOpTypes.SET, 'value') + } + } + }) + + this[ReactiveFlags.IS_READONLY] = isReadonly + } + + [RefSymbol]: true + + get value() { + if (this._dirty) { + this._value = this.effect() + this._dirty = false + } + track(this, TrackOpTypes.GET, 'value') + return this._value + } + + set value(newValue: T) { + this._setter(newValue) + } + + get __v_isRef() { + return true + } +} + export function computed(getter: ComputedGetter): ComputedRef export function computed( options: WritableComputedOptions @@ -42,37 +88,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 _ComputedRef( + getter, + setter, + isFunction(getterOrOptions) || !getterOrOptions.set + ) } diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index cf0eb7c234b..cf87617e8e9 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -4,7 +4,7 @@ import { isArray, isObject, hasChanged } from '@vue/shared' import { reactive, isProxy, toRaw, isReactive } from './reactive' import { CollectionTypes } from './collectionHandlers' -declare const RefSymbol: unique symbol +export declare const RefSymbol: unique symbol export interface Ref { /** @@ -47,7 +47,7 @@ export function shallowRef(value?: unknown) { class _Ref implements Ref { private _value: T - constructor(private _rawValue: T, private _shallow = false) { + constructor(private _rawValue: T, private readonly _shallow = false) { this._value = _shallow ? _rawValue : convert(_rawValue) } @@ -159,7 +159,7 @@ export function toRefs(object: T): ToRefs { } class _ObjectRef implements Ref { - constructor(private _object: T, private _key: K) {} + constructor(private readonly _object: T, private readonly _key: K) {} get value() { return this._object[this._key] From 098138a270fd553f3624921a624a8b5b728b3681 Mon Sep 17 00:00:00 2001 From: Robbin Baauw Date: Wed, 19 Aug 2020 18:02:49 +0200 Subject: [PATCH 3/5] perf: improve isRef performance --- packages/reactivity/src/computed.ts | 8 +++----- packages/reactivity/src/ref.ts | 20 +++++++------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 3642139d129..c8830b31bda 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,6 +1,6 @@ import { effect, ReactiveEffect, trigger, track } from './effect' import { TriggerOpTypes, TrackOpTypes } from './operations' -import { Ref, RefSymbol } from './ref' +import { Ref } from './ref' import { isFunction, NOOP } from '@vue/shared' import { ReactiveFlags } from './reactive' @@ -20,7 +20,7 @@ export interface WritableComputedOptions { set: ComputedSetter } -class _ComputedRef implements ComputedRef { +class _ComputedRef { private _value!: T private _dirty = true @@ -46,8 +46,6 @@ class _ComputedRef implements ComputedRef { this[ReactiveFlags.IS_READONLY] = isReadonly } - [RefSymbol]: true - get value() { if (this._dirty) { this._value = this.effect() @@ -92,5 +90,5 @@ export function computed( getter, setter, isFunction(getterOrOptions) || !getterOrOptions.set - ) + ) as any } diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index cf87617e8e9..18614703d43 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -4,7 +4,7 @@ import { isArray, isObject, hasChanged } from '@vue/shared' import { reactive, isProxy, toRaw, isReactive } from './reactive' import { CollectionTypes } from './collectionHandlers' -export declare const RefSymbol: unique symbol +declare const RefSymbol: unique symbol export interface Ref { /** @@ -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,7 +44,7 @@ export function shallowRef(value?: unknown) { return createRef(value, true) } -class _Ref implements Ref { +class _Ref { private _value: T constructor(private _rawValue: T, private readonly _shallow = false) { @@ -64,8 +64,6 @@ class _Ref implements Ref { } } - [RefSymbol]: true - get __v_isRef() { return true } @@ -115,7 +113,7 @@ export type CustomRefFactory = ( set: (value: T) => void } -class _CustomRef implements Ref { +class _CustomRef { private readonly _get: ReturnType>['get'] private readonly _set: ReturnType>['set'] @@ -136,15 +134,13 @@ class _CustomRef implements Ref { this._set(newVal) } - [RefSymbol]: true - get __v_isRef() { return true } } export function customRef(factory: CustomRefFactory): Ref { - return new _CustomRef(factory) + return new _CustomRef(factory) as any } export function toRefs(object: T): ToRefs { @@ -158,7 +154,7 @@ export function toRefs(object: T): ToRefs { return ret } -class _ObjectRef implements Ref { +class _ObjectRef { constructor(private readonly _object: T, private readonly _key: K) {} get value() { @@ -169,8 +165,6 @@ class _ObjectRef implements Ref { this._object[this._key] = newVal } - [RefSymbol]: true - get __v_isRef() { return true } @@ -180,7 +174,7 @@ export function toRef( object: T, key: K ): Ref { - return new _ObjectRef(object, key) + return new _ObjectRef(object, key) as any } // corner case when use narrows type From 58195f0b0b2dfb469899f1142fe344eed1788046 Mon Sep 17 00:00:00 2001 From: Robbin Baauw Date: Wed, 19 Aug 2020 20:25:29 +0200 Subject: [PATCH 4/5] fix(ref): pass raw class to trigger/track --- packages/reactivity/src/computed.ts | 6 +++--- packages/reactivity/src/ref.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index c8830b31bda..ea7a9fba951 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 @@ -38,7 +38,7 @@ class _ComputedRef { scheduler: () => { if (!this._dirty) { this._dirty = true - trigger(this, TriggerOpTypes.SET, 'value') + trigger(toRaw(this), TriggerOpTypes.SET, 'value') } } }) @@ -51,7 +51,7 @@ class _ComputedRef { this._value = this.effect() this._dirty = false } - track(this, TrackOpTypes.GET, 'value') + track(toRaw(this), TrackOpTypes.GET, 'value') return this._value } diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 18614703d43..fdbc99d4e43 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -52,7 +52,7 @@ class _Ref { } get value() { - track(this, TrackOpTypes.GET, 'value') + track(toRaw(this), TrackOpTypes.GET, 'value') return this._value } @@ -60,7 +60,7 @@ class _Ref { if (hasChanged(toRaw(newVal), this._rawValue)) { this._rawValue = newVal this._value = this._shallow ? newVal : convert(newVal) - trigger(this, TriggerOpTypes.SET, 'value', newVal) + trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal) } } From 4da3bf08a8e38ad17da13474ccb0fa0dda203fcc Mon Sep 17 00:00:00 2001 From: Robbin Baauw Date: Thu, 20 Aug 2020 17:27:55 +0200 Subject: [PATCH 5/5] fix(ref): merge review comments --- packages/reactivity/src/computed.ts | 13 +++++-------- packages/reactivity/src/ref.ts | 30 ++++++++++++----------------- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index ea7a9fba951..7f2c5b50d80 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -20,13 +20,14 @@ export interface WritableComputedOptions { set: ComputedSetter } -class _ComputedRef { +class ComputedRefImpl { private _value!: T private _dirty = true - public readonly effect: ReactiveEffect; + public readonly effect: ReactiveEffect - public [ReactiveFlags.IS_READONLY]: boolean + public readonly __v_isRef = true; + public readonly [ReactiveFlags.IS_READONLY]: boolean constructor( getter: ComputedGetter, @@ -58,10 +59,6 @@ class _ComputedRef { set value(newValue: T) { this._setter(newValue) } - - get __v_isRef() { - return true - } } export function computed(getter: ComputedGetter): ComputedRef @@ -86,7 +83,7 @@ export function computed( setter = getterOrOptions.set } - return new _ComputedRef( + return new ComputedRefImpl( getter, setter, isFunction(getterOrOptions) || !getterOrOptions.set diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index fdbc99d4e43..64d9f1073ae 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -44,9 +44,11 @@ export function shallowRef(value?: unknown) { return createRef(value, true) } -class _Ref { +class RefImpl { private _value: T + public readonly __v_isRef = true + constructor(private _rawValue: T, private readonly _shallow = false) { this._value = _shallow ? _rawValue : convert(_rawValue) } @@ -63,17 +65,13 @@ class _Ref { trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal) } } - - get __v_isRef() { - return true - } } function createRef(rawValue: unknown, shallow = false) { if (isRef(rawValue)) { return rawValue } - return new _Ref(rawValue, shallow) + return new RefImpl(rawValue, shallow) } export function triggerRef(ref: Ref) { @@ -113,10 +111,12 @@ export type CustomRefFactory = ( set: (value: T) => void } -class _CustomRef { +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'), @@ -133,14 +133,10 @@ class _CustomRef { set value(newVal) { this._set(newVal) } - - get __v_isRef() { - return true - } } export function customRef(factory: CustomRefFactory): Ref { - return new _CustomRef(factory) as any + return new CustomRefImpl(factory) as any } export function toRefs(object: T): ToRefs { @@ -154,7 +150,9 @@ export function toRefs(object: T): ToRefs { return ret } -class _ObjectRef { +class ObjectRefImpl { + public readonly __v_isRef = true + constructor(private readonly _object: T, private readonly _key: K) {} get value() { @@ -164,17 +162,13 @@ class _ObjectRef { set value(newVal) { this._object[this._key] = newVal } - - get __v_isRef() { - return true - } } export function toRef( object: T, key: K ): Ref { - return new _ObjectRef(object, key) as any + return new ObjectRefImpl(object, key) as any } // corner case when use narrows type