mirror of https://github.com/vuejs/core.git
feat(dx): warn users when computed is self-triggering (#10299)
This commit is contained in:
parent
b99ac931a8
commit
f7ba97f975
|
@ -14,6 +14,7 @@ import {
|
||||||
toRaw,
|
toRaw,
|
||||||
} from '../src'
|
} from '../src'
|
||||||
import { DirtyLevels } from '../src/constants'
|
import { DirtyLevels } from '../src/constants'
|
||||||
|
import { COMPUTED_SIDE_EFFECT_WARN } from '../src/computed'
|
||||||
|
|
||||||
describe('reactivity/computed', () => {
|
describe('reactivity/computed', () => {
|
||||||
it('should return updated value', () => {
|
it('should return updated value', () => {
|
||||||
|
@ -488,6 +489,7 @@ describe('reactivity/computed', () => {
|
||||||
expect(c3.effect._dirtyLevel).toBe(
|
expect(c3.effect._dirtyLevel).toBe(
|
||||||
DirtyLevels.MaybeDirty_ComputedSideEffect,
|
DirtyLevels.MaybeDirty_ComputedSideEffect,
|
||||||
)
|
)
|
||||||
|
expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should work when chained(ref+computed)', () => {
|
it('should work when chained(ref+computed)', () => {
|
||||||
|
@ -502,6 +504,7 @@ describe('reactivity/computed', () => {
|
||||||
expect(c2.value).toBe('0foo')
|
expect(c2.value).toBe('0foo')
|
||||||
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
|
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
|
||||||
expect(c2.value).toBe('1foo')
|
expect(c2.value).toBe('1foo')
|
||||||
|
expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should trigger effect even computed already dirty', () => {
|
it('should trigger effect even computed already dirty', () => {
|
||||||
|
@ -524,6 +527,7 @@ describe('reactivity/computed', () => {
|
||||||
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
|
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
|
||||||
v.value = 2
|
v.value = 2
|
||||||
expect(fnSpy).toBeCalledTimes(2)
|
expect(fnSpy).toBeCalledTimes(2)
|
||||||
|
expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
// #10185
|
// #10185
|
||||||
|
@ -567,6 +571,7 @@ describe('reactivity/computed', () => {
|
||||||
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
|
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
|
||||||
|
|
||||||
expect(c3.value).toBe('yes')
|
expect(c3.value).toBe('yes')
|
||||||
|
expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be not dirty after deps mutate (mutate deps in computed)', async () => {
|
it('should be not dirty after deps mutate (mutate deps in computed)', async () => {
|
||||||
|
@ -588,6 +593,7 @@ describe('reactivity/computed', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(serializeInner(root)).toBe(`2`)
|
expect(serializeInner(root)).toBe(`2`)
|
||||||
|
expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not trigger effect scheduler by recurse computed effect', async () => {
|
it('should not trigger effect scheduler by recurse computed effect', async () => {
|
||||||
|
@ -610,5 +616,6 @@ describe('reactivity/computed', () => {
|
||||||
v.value += ' World'
|
v.value += ' World'
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(serializeInner(root)).toBe('Hello World World World World')
|
expect(serializeInner(root)).toBe('Hello World World World World')
|
||||||
|
expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { NOOP, hasChanged, isFunction } from '@vue/shared'
|
||||||
import { toRaw } from './reactive'
|
import { toRaw } from './reactive'
|
||||||
import type { Dep } from './dep'
|
import type { Dep } from './dep'
|
||||||
import { DirtyLevels, ReactiveFlags } from './constants'
|
import { DirtyLevels, ReactiveFlags } from './constants'
|
||||||
|
import { warn } from './warning'
|
||||||
|
|
||||||
declare const ComputedRefSymbol: unique symbol
|
declare const ComputedRefSymbol: unique symbol
|
||||||
|
|
||||||
|
@ -24,6 +25,12 @@ export interface WritableComputedOptions<T> {
|
||||||
set: ComputedSetter<T>
|
set: ComputedSetter<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const COMPUTED_SIDE_EFFECT_WARN =
|
||||||
|
`Computed is still dirty after getter evaluation,` +
|
||||||
|
` likely because a computed is mutating its own dependency in its getter.` +
|
||||||
|
` State mutations in computed getters should be avoided. ` +
|
||||||
|
` Check the docs for more details: https://vuejs.org/guide/essentials/computed.html#getters-should-be-side-effect-free`
|
||||||
|
|
||||||
export class ComputedRefImpl<T> {
|
export class ComputedRefImpl<T> {
|
||||||
public dep?: Dep = undefined
|
public dep?: Dep = undefined
|
||||||
|
|
||||||
|
@ -67,6 +74,7 @@ export class ComputedRefImpl<T> {
|
||||||
}
|
}
|
||||||
trackRefValue(self)
|
trackRefValue(self)
|
||||||
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty_ComputedSideEffect) {
|
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty_ComputedSideEffect) {
|
||||||
|
__DEV__ && warn(COMPUTED_SIDE_EFFECT_WARN)
|
||||||
triggerRefValue(self, DirtyLevels.MaybeDirty_ComputedSideEffect)
|
triggerRefValue(self, DirtyLevels.MaybeDirty_ComputedSideEffect)
|
||||||
}
|
}
|
||||||
return self._value
|
return self._value
|
||||||
|
@ -141,7 +149,7 @@ export function computed<T>(
|
||||||
getter = getterOrOptions
|
getter = getterOrOptions
|
||||||
setter = __DEV__
|
setter = __DEV__
|
||||||
? () => {
|
? () => {
|
||||||
console.warn('Write operation failed: computed value is readonly')
|
warn('Write operation failed: computed value is readonly')
|
||||||
}
|
}
|
||||||
: NOOP
|
: NOOP
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue