]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(reactivity): should not trigger watch on computed ref when value is unchanged
authorEvan You <yyx990803@gmail.com>
Tue, 6 Oct 2020 22:16:20 +0000 (18:16 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 6 Oct 2020 22:16:20 +0000 (18:16 -0400)
fix #2231

packages/reactivity/src/ref.ts
packages/runtime-core/__tests__/apiWatch.spec.ts
packages/runtime-core/src/apiWatch.ts

index 6596ecddb01c926faafc09980de92346cff7f850..04464fdff432eb744d89e9c2420da71d57b2f82d 100644 (file)
@@ -7,13 +7,17 @@ import { CollectionTypes } from './collectionHandlers'
 declare const RefSymbol: unique symbol
 
 export interface Ref<T = any> {
+  value: T
   /**
    * Type differentiator only.
    * We need this to be in public d.ts but don't want it to show up in IDE
    * autocomplete, so we use a private Symbol instead.
    */
   [RefSymbol]: true
-  value: T
+  /**
+   * @internal
+   */
+  _shallow?: boolean
 }
 
 export type ToRefs<T = any> = { [K in keyof T]: Ref<T[K]> }
@@ -49,7 +53,7 @@ class RefImpl<T> {
 
   public readonly __v_isRef = true
 
-  constructor(private _rawValue: T, private readonly _shallow = false) {
+  constructor(private _rawValue: T, public readonly _shallow = false) {
     this._value = _shallow ? _rawValue : convert(_rawValue)
   }
 
@@ -75,7 +79,7 @@ function createRef(rawValue: unknown, shallow = false) {
 }
 
 export function triggerRef(ref: Ref) {
-  trigger(ref, TriggerOpTypes.SET, 'value', __DEV__ ? ref.value : void 0)
+  trigger(toRaw(ref), TriggerOpTypes.SET, 'value', __DEV__ ? ref.value : void 0)
 }
 
 export function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
index 31bca6bed3fb186653b26ea4c553a1347adc0987..c96db41fce4cc1e8c146ba94ba334c2ff09d720a 100644 (file)
@@ -13,7 +13,8 @@ import {
   DebuggerEvent,
   TrackOpTypes,
   TriggerOpTypes,
-  triggerRef
+  triggerRef,
+  shallowRef
 } from '@vue/reactivity'
 
 // reference: https://vue-composition-api-rfc.netlify.com/api.html#watch
@@ -750,8 +751,8 @@ describe('api: watch', () => {
     expect(calls).toBe(1)
   })
 
-  test('should force trigger on triggerRef when watching a ref', async () => {
-    const v = ref({ a: 1 })
+  test('should force trigger on triggerRef when watching a shallow ref', async () => {
+    const v = shallowRef({ a: 1 })
     let sideEffect = 0
     watch(v, obj => {
       sideEffect = obj.a
@@ -785,4 +786,17 @@ describe('api: watch', () => {
     await nextTick()
     expect(spy).toHaveBeenCalledTimes(1)
   })
+
+  // #2231
+  test('computed refs should not trigger watch if value has no change', async () => {
+    const spy = jest.fn()
+    const source = ref(0)
+    const price = computed(() => source.value === 0)
+    watch(price, spy)
+    source.value++
+    await nextTick()
+    source.value++
+    await nextTick()
+    expect(spy).toHaveBeenCalledTimes(1)
+  })
 })
index 14253a2a403469bd4c4de75649711cf02a4ca2f6..1e5a44f55ab3948d57dadba0173d51de67209593 100644 (file)
@@ -161,9 +161,10 @@ function doWatch(
   }
 
   let getter: () => any
-  const isRefSource = isRef(source)
-  if (isRefSource) {
+  let forceTrigger = false
+  if (isRef(source)) {
     getter = () => (source as Ref).value
+    forceTrigger = !!(source as Ref)._shallow
   } else if (isReactive(source)) {
     getter = () => source
     deep = true
@@ -242,7 +243,7 @@ function doWatch(
     if (cb) {
       // watch(source, cb)
       const newValue = runner()
-      if (deep || isRefSource || hasChanged(newValue, oldValue)) {
+      if (deep || forceTrigger || hasChanged(newValue, oldValue)) {
         // cleanup before running cb again
         if (cleanup) {
           cleanup()