Skip to content

Commit 6b96e09

Browse files
johnsoncodehklynxlangya
authored andcommitted
fix(reactivity): avoid infinite recursion from side effects in computed getter (vuejs#10232)
close vuejs#10214
1 parent a778d0e commit 6b96e09

File tree

5 files changed

+58
-58
lines changed

5 files changed

+58
-58
lines changed

packages/reactivity/__tests__/computed.spec.ts

+34-4
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,12 @@ describe('reactivity/computed', () => {
482482
c3.value
483483

484484
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
485-
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
486-
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
485+
expect(c2.effect._dirtyLevel).toBe(
486+
DirtyLevels.MaybeDirty_ComputedSideEffect,
487+
)
488+
expect(c3.effect._dirtyLevel).toBe(
489+
DirtyLevels.MaybeDirty_ComputedSideEffect,
490+
)
487491
})
488492

489493
it('should work when chained(ref+computed)', () => {
@@ -550,8 +554,12 @@ describe('reactivity/computed', () => {
550554

551555
c3.value
552556
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
553-
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
554-
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
557+
expect(c2.effect._dirtyLevel).toBe(
558+
DirtyLevels.MaybeDirty_ComputedSideEffect,
559+
)
560+
expect(c3.effect._dirtyLevel).toBe(
561+
DirtyLevels.MaybeDirty_ComputedSideEffect,
562+
)
555563

556564
v1.value.v.value = 999
557565
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
@@ -581,4 +589,26 @@ describe('reactivity/computed', () => {
581589
await nextTick()
582590
expect(serializeInner(root)).toBe(`2`)
583591
})
592+
593+
it('should not trigger effect scheduler by recurse computed effect', async () => {
594+
const v = ref('Hello')
595+
const c = computed(() => {
596+
v.value += ' World'
597+
return v.value
598+
})
599+
const Comp = {
600+
setup: () => {
601+
return () => c.value
602+
},
603+
}
604+
const root = nodeOps.createElement('div')
605+
606+
render(h(Comp), root)
607+
await nextTick()
608+
expect(serializeInner(root)).toBe('Hello World')
609+
610+
v.value += ' World'
611+
await nextTick()
612+
expect(serializeInner(root)).toBe('Hello World World World World')
613+
})
584614
})

packages/reactivity/src/computed.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,13 @@ export class ComputedRefImpl<T> {
4343
) {
4444
this.effect = new ReactiveEffect(
4545
() => getter(this._value),
46-
() => triggerRefValue(this, DirtyLevels.MaybeDirty),
46+
() =>
47+
triggerRefValue(
48+
this,
49+
this.effect._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect
50+
? DirtyLevels.MaybeDirty_ComputedSideEffect
51+
: DirtyLevels.MaybeDirty,
52+
),
4753
)
4854
this.effect.computed = this
4955
this.effect.active = this._cacheable = !isSSR
@@ -60,8 +66,8 @@ export class ComputedRefImpl<T> {
6066
triggerRefValue(self, DirtyLevels.Dirty)
6167
}
6268
trackRefValue(self)
63-
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty) {
64-
triggerRefValue(self, DirtyLevels.MaybeDirty)
69+
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty_ComputedSideEffect) {
70+
triggerRefValue(self, DirtyLevels.MaybeDirty_ComputedSideEffect)
6571
}
6672
return self._value
6773
}

packages/reactivity/src/constants.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export enum ReactiveFlags {
3636
export enum DirtyLevels {
3737
NotDirty = 0,
3838
QueryingDirty = 1,
39-
MaybeDirty = 2,
40-
Dirty = 3,
39+
MaybeDirty_ComputedSideEffect = 2,
40+
MaybeDirty = 3,
41+
Dirty = 4,
4142
}

packages/reactivity/src/effect.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ export class ReactiveEffect<T = any> {
7676
}
7777

7878
public get dirty() {
79-
if (this._dirtyLevel === DirtyLevels.MaybeDirty) {
79+
if (
80+
this._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect ||
81+
this._dirtyLevel === DirtyLevels.MaybeDirty
82+
) {
8083
this._dirtyLevel = DirtyLevels.QueryingDirty
8184
pauseTracking()
8285
for (let i = 0; i < this._depsLength; i++) {
@@ -310,7 +313,10 @@ export function triggerEffects(
310313
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
311314
}
312315
effect.trigger()
313-
if (!effect._runnings || effect.allowRecurse) {
316+
if (
317+
(!effect._runnings || effect.allowRecurse) &&
318+
effect._dirtyLevel !== DirtyLevels.MaybeDirty_ComputedSideEffect
319+
) {
314320
effect._shouldSchedule = false
315321
if (effect.scheduler) {
316322
queueEffectSchedulers.push(effect.scheduler)

packages/reactivity/src/ref.ts

+4-47
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,15 @@ type RefBase<T> = {
4444
value: T
4545
}
4646

47-
/**
48-
* trackRefValue 用于跟踪依赖
49-
* @param ref
50-
*/
5147
export function trackRefValue(ref: RefBase<any>) {
52-
// 只有在需要跟踪且当前有活跃的effect时,才会进行依赖收集
5348
if (shouldTrack && activeEffect) {
54-
// 获取ref的原始值,去除响应式对象的包装
5549
ref = toRaw(ref)
56-
// trackEffect 用于建立当前活跃的effect与ref之间的依赖关系
5750
trackEffect(
58-
// 当前活跃的effect
5951
activeEffect,
60-
// 获取或创建 ref 的依赖管理器 dep
61-
ref.dep ||
62-
(ref.dep = createDep(
63-
() => (ref.dep = undefined),
64-
ref instanceof ComputedRefImpl ? ref : undefined,
65-
)),
66-
67-
// 开发模式,提供额外调试信息
52+
(ref.dep ??= createDep(
53+
() => (ref.dep = undefined),
54+
ref instanceof ComputedRefImpl ? ref : undefined,
55+
)),
6856
__DEV__
6957
? {
7058
target: ref,
@@ -76,23 +64,14 @@ export function trackRefValue(ref: RefBase<any>) {
7664
}
7765
}
7866

79-
/**
80-
* triggerRefValue 用于触发更新
81-
* @param ref
82-
* @param dirtyLevel
83-
* @param newVal
84-
*/
8567
export function triggerRefValue(
8668
ref: RefBase<any>,
8769
dirtyLevel: DirtyLevels = DirtyLevels.Dirty,
8870
newVal?: any,
8971
) {
90-
// 获取ref的原始值,去除响应式对象的包装
9172
ref = toRaw(ref)
92-
// 获取 ref 的依赖管理器
9373
const dep = ref.dep
9474
if (dep) {
95-
// triggerEffects 用于出发所有依赖此 ref 的 effect 更新
9675
triggerEffects(
9776
dep,
9877
dirtyLevel,
@@ -169,58 +148,36 @@ function createRef(rawValue: unknown, shallow: boolean) {
169148
if (isRef(rawValue)) {
170149
return rawValue
171150
}
172-
debugger
173151
return new RefImpl(rawValue, shallow)
174152
}
175153

176154
class RefImpl<T> {
177-
// 存储ref的值
178155
private _value: T
179-
// 存储ref的原始值
180156
private _rawValue: T
181157

182-
// 这是一个依赖对象,用于收集依赖,当依赖发生变化时,触发更新
183158
public dep?: Dep = undefined
184-
// 用于标识是否是一个ref对象
185159
public readonly __v_isRef = true
186160

187161
constructor(
188162
value: T,
189163
public readonly __v_isShallow: boolean,
190164
) {
191-
// 使用__v_isShallow标识是否是一个浅层的ref
192-
// 如果是浅层的ref,那么value就是原始值,否则就是一个响应式对象
193165
this._rawValue = __v_isShallow ? value : toRaw(value)
194166
this._value = __v_isShallow ? value : toReactive(value)
195167
}
196168

197-
// 获取ref的值
198-
// 如果ref的值是一个响应式对象,那么会对这个响应式对象进行依赖追踪
199169
get value() {
200-
debugger
201170
trackRefValue(this)
202171
return this._value
203172
}
204173

205-
// 设置ref的值
206-
// 在设置值时,会根据条件决定是否需要转换值
207174
set value(newVal) {
208-
debugger
209-
// 确定是否可以直接使用newVal
210-
// 取决于 __v_isShallow、newVal是否是一个浅层或只读的响应式对象
211175
const useDirectValue =
212176
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
213-
214-
// 根据 useDirectValue 判断是否需要将 newVal 转换为原始值
215177
newVal = useDirectValue ? newVal : toRaw(newVal)
216-
217-
// 判断新值是否发生了变化
218178
if (hasChanged(newVal, this._rawValue)) {
219-
// 如果发生了变化,那么就更新值
220179
this._rawValue = newVal
221180
this._value = useDirectValue ? newVal : toReactive(newVal)
222-
223-
// 触发更新
224181
triggerRefValue(this, DirtyLevels.Dirty, newVal)
225182
}
226183
}

0 commit comments

Comments
 (0)