Browse Source

perf(reactivity): ref should not trigger if value did not change

Note: shallowRef will always trigger on assignment because it does not
account for deep mutations

close #1012
Evan You 6 years ago
parent
commit
b0d4df9743
2 changed files with 40 additions and 15 deletions
  1. 24 0
      packages/reactivity/__tests__/ref.spec.ts
  2. 16 15
      packages/reactivity/src/ref.ts

+ 24 - 0
packages/reactivity/__tests__/ref.spec.ts

@@ -22,11 +22,19 @@ describe('reactivity/ref', () => {
   it('should be reactive', () => {
     const a = ref(1)
     let dummy
+    let calls = 0
     effect(() => {
+      calls++
       dummy = a.value
     })
+    expect(calls).toBe(1)
     expect(dummy).toBe(1)
     a.value = 2
+    expect(calls).toBe(2)
+    expect(dummy).toBe(2)
+    // same value should not trigger
+    a.value = 2
+    expect(calls).toBe(2)
     expect(dummy).toBe(2)
   })
 
@@ -174,6 +182,22 @@ describe('reactivity/ref', () => {
     expect(dummy).toBe(2)
   })
 
+  test('shallowRef force trigger', () => {
+    const sref = shallowRef({ a: 1 })
+    let dummy
+    effect(() => {
+      dummy = sref.value.a
+    })
+    expect(dummy).toBe(1)
+
+    sref.value.a = 2
+    expect(dummy).toBe(1) // should not trigger yet
+
+    // force trigger
+    sref.value = sref.value
+    expect(dummy).toBe(2)
+  })
+
   test('isRef', () => {
     expect(isRef(ref(1))).toBe(true)
     expect(isRef(computed(() => 1))).toBe(true)

+ 16 - 15
packages/reactivity/src/ref.ts

@@ -1,7 +1,7 @@
 import { track, trigger } from './effect'
 import { TrackOpTypes, TriggerOpTypes } from './operations'
-import { isObject } from '@vue/shared'
-import { reactive, isProxy } from './reactive'
+import { isObject, hasChanged } from '@vue/shared'
+import { reactive, isProxy, toRaw } from './reactive'
 import { ComputedRef } from './computed'
 import { CollectionTypes } from './collectionHandlers'
 
@@ -43,13 +43,11 @@ export function shallowRef(value?: unknown) {
   return createRef(value, true)
 }
 
-function createRef(value: unknown, shallow = false) {
-  if (isRef(value)) {
-    return value
-  }
-  if (!shallow) {
-    value = convert(value)
+function createRef(rawValue: unknown, shallow = false) {
+  if (isRef(rawValue)) {
+    return rawValue
   }
+  let value = shallow ? rawValue : convert(rawValue)
   const r = {
     _isRef: true,
     get value() {
@@ -57,13 +55,16 @@ function createRef(value: unknown, shallow = false) {
       return value
     },
     set value(newVal) {
-      value = shallow ? newVal : convert(newVal)
-      trigger(
-        r,
-        TriggerOpTypes.SET,
-        'value',
-        __DEV__ ? { newValue: newVal } : void 0
-      )
+      if (shallow || hasChanged(toRaw(newVal), rawValue)) {
+        rawValue = newVal
+        value = shallow ? newVal : convert(newVal)
+        trigger(
+          r,
+          TriggerOpTypes.SET,
+          'value',
+          __DEV__ ? { newValue: newVal } : void 0
+        )
+      }
     }
   }
   return r