Skip to content

Commit 390589e

Browse files
committed
fix(reactivity): should not trigger watch on computed ref when value is unchanged
fix #2231
1 parent a66e53a commit 390589e

File tree

3 files changed

+28
-9
lines changed

3 files changed

+28
-9
lines changed

packages/reactivity/src/ref.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ import { CollectionTypes } from './collectionHandlers'
77
declare const RefSymbol: unique symbol
88

99
export interface Ref<T = any> {
10+
value: T
1011
/**
1112
* Type differentiator only.
1213
* We need this to be in public d.ts but don't want it to show up in IDE
1314
* autocomplete, so we use a private Symbol instead.
1415
*/
1516
[RefSymbol]: true
16-
value: T
17+
/**
18+
* @internal
19+
*/
20+
_shallow?: boolean
1721
}
1822

1923
export type ToRefs<T = any> = { [K in keyof T]: Ref<T[K]> }
@@ -49,7 +53,7 @@ class RefImpl<T> {
4953

5054
public readonly __v_isRef = true
5155

52-
constructor(private _rawValue: T, private readonly _shallow = false) {
56+
constructor(private _rawValue: T, public readonly _shallow = false) {
5357
this._value = _shallow ? _rawValue : convert(_rawValue)
5458
}
5559

@@ -75,7 +79,7 @@ function createRef(rawValue: unknown, shallow = false) {
7579
}
7680

7781
export function triggerRef(ref: Ref) {
78-
trigger(ref, TriggerOpTypes.SET, 'value', __DEV__ ? ref.value : void 0)
82+
trigger(toRaw(ref), TriggerOpTypes.SET, 'value', __DEV__ ? ref.value : void 0)
7983
}
8084

8185
export function unref<T>(ref: T): T extends Ref<infer V> ? V : T {

packages/runtime-core/__tests__/apiWatch.spec.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
DebuggerEvent,
1414
TrackOpTypes,
1515
TriggerOpTypes,
16-
triggerRef
16+
triggerRef,
17+
shallowRef
1718
} from '@vue/reactivity'
1819

1920
// reference: https://vue-composition-api-rfc.netlify.com/api.html#watch
@@ -750,8 +751,8 @@ describe('api: watch', () => {
750751
expect(calls).toBe(1)
751752
})
752753

753-
test('should force trigger on triggerRef when watching a ref', async () => {
754-
const v = ref({ a: 1 })
754+
test('should force trigger on triggerRef when watching a shallow ref', async () => {
755+
const v = shallowRef({ a: 1 })
755756
let sideEffect = 0
756757
watch(v, obj => {
757758
sideEffect = obj.a
@@ -785,4 +786,17 @@ describe('api: watch', () => {
785786
await nextTick()
786787
expect(spy).toHaveBeenCalledTimes(1)
787788
})
789+
790+
// #2231
791+
test('computed refs should not trigger watch if value has no change', async () => {
792+
const spy = jest.fn()
793+
const source = ref(0)
794+
const price = computed(() => source.value === 0)
795+
watch(price, spy)
796+
source.value++
797+
await nextTick()
798+
source.value++
799+
await nextTick()
800+
expect(spy).toHaveBeenCalledTimes(1)
801+
})
788802
})

packages/runtime-core/src/apiWatch.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,10 @@ function doWatch(
161161
}
162162

163163
let getter: () => any
164-
const isRefSource = isRef(source)
165-
if (isRefSource) {
164+
let forceTrigger = false
165+
if (isRef(source)) {
166166
getter = () => (source as Ref).value
167+
forceTrigger = !!(source as Ref)._shallow
167168
} else if (isReactive(source)) {
168169
getter = () => source
169170
deep = true
@@ -242,7 +243,7 @@ function doWatch(
242243
if (cb) {
243244
// watch(source, cb)
244245
const newValue = runner()
245-
if (deep || isRefSource || hasChanged(newValue, oldValue)) {
246+
if (deep || forceTrigger || hasChanged(newValue, oldValue)) {
246247
// cleanup before running cb again
247248
if (cleanup) {
248249
cleanup()

0 commit comments

Comments
 (0)