]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
perf(reactivity): improve ref performance by using class-based implementation (#1900)
authorRobbin Baauw <robbin.baauw@gmail.com>
Fri, 21 Aug 2020 17:47:41 +0000 (19:47 +0200)
committerGitHub <noreply@github.com>
Fri, 21 Aug 2020 17:47:41 +0000 (13:47 -0400)
packages/reactivity/src/computed.ts
packages/reactivity/src/ref.ts

index 32740f983b1acbeb8c762f15d4d124270085654d..7f2c5b50d8065d4bd122e89db033121e905fdc2d 100644 (file)
@@ -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<T = any> extends WritableComputedRef<T> {
   readonly value: T
@@ -20,6 +20,47 @@ export interface WritableComputedOptions<T> {
   set: ComputedSetter<T>
 }
 
+class ComputedRefImpl<T> {
+  private _value!: T
+  private _dirty = true
+
+  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 = 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<T>(getter: ComputedGetter<T>): ComputedRef<T>
 export function computed<T>(
   options: WritableComputedOptions<T>
@@ -42,37 +83,9 @@ export function computed<T>(
     setter = getterOrOptions.set
   }
 
-  let dirty = true
-  let value: T
-  let computed: ComputedRef<T>
-
-  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
 }
index 83bef572231992c3624976bf6102c28d47f410be..64d9f1073ae572220c861ad6ff07d78a372b841f 100644 (file)
@@ -23,7 +23,7 @@ const convert = <T extends unknown>(val: T): T =>
 
 export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
 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<T extends object>(
@@ -44,26 +44,34 @@ export function shallowRef(value?: unknown) {
   return createRef(value, true)
 }
 
+class RefImpl<T> {
+  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<T> = (
   set: (value: T) => void
 }
 
-export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
-  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<T> {
+  private readonly _get: ReturnType<CustomRefFactory<T>>['get']
+  private readonly _set: ReturnType<CustomRefFactory<T>>['set']
+
+  public readonly __v_isRef = true
+
+  constructor(factory: CustomRefFactory<T>) {
+    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<T>(factory: CustomRefFactory<T>): Ref<T> {
+  return new CustomRefImpl(factory) as any
 }
 
 export function toRefs<T extends object>(object: T): ToRefs<T> {
@@ -131,19 +150,25 @@ export function toRefs<T extends object>(object: T): ToRefs<T> {
   return ret
 }
 
+class ObjectRefImpl<T extends object, K extends keyof T> {
+  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<T extends object, K extends keyof T>(
   object: T,
   key: K
 ): Ref<T[K]> {
-  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