Browse Source

perf(reactivity): avoid triggering re-render if computed value did not change

Evan You 5 years ago
parent
commit
ebaac9a56d

+ 36 - 1
packages/reactivity/src/computed.ts

@@ -5,6 +5,7 @@ import { ReactiveFlags, toRaw } from './reactive'
 
 export interface ComputedRef<T = any> extends WritableComputedRef<T> {
   readonly value: T
+  defer?: (fn: () => void) => void
 }
 
 export interface WritableComputedRef<T> extends Ref<T> {
@@ -19,6 +20,16 @@ export interface WritableComputedOptions<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?: Set<ReactiveEffect> = undefined
 
@@ -35,10 +46,34 @@ class ComputedRefImpl<T> {
     private readonly _setter: ComputedSetter<T>,
     isReadonly: boolean
   ) {
+    let deferFn: () => void
+    let scheduled = false
     this.effect = new ReactiveEffect(getter, () => {
       if (!this._dirty) {
         this._dirty = true
-        triggerRefValue(this)
+        if (scheduler) {
+          if (!scheduled) {
+            scheduled = true
+            scheduler(
+              deferFn ||
+                (deferFn = () => {
+                  scheduled = false
+                  if (this._dirty) {
+                    this._dirty = false
+                    const newValue = this.effect.run()!
+                    if (this._value !== newValue) {
+                      this._value = newValue
+                      triggerRefValue(this)
+                    }
+                  } else {
+                    triggerRefValue(this)
+                  }
+                })
+            )
+          }
+        } else {
+          triggerRefValue(this)
+        }
       }
     })
     this[ReactiveFlags.IS_READONLY] = isReadonly

+ 1 - 0
packages/reactivity/src/index.ts

@@ -30,6 +30,7 @@ export {
 } from './reactive'
 export {
   computed,
+  setComputedScheduler,
   ComputedRef,
   WritableComputedRef,
   WritableComputedOptions,

+ 4 - 0
packages/runtime-core/src/scheduler.ts

@@ -2,6 +2,10 @@ import { ErrorCodes, callWithErrorHandling } from './errorHandling'
 import { isArray } from '@vue/shared'
 import { ComponentInternalInstance, getComponentName } from './component'
 import { warn } from './warning'
+import { setComputedScheduler } from '@vue/reactivity'
+
+// set scheduler for computed
+setComputedScheduler(queueJob)
 
 export interface SchedulerJob extends Function {
   id?: number