diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 9d60ad352c4..5583e1f2533 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -5,7 +5,9 @@ import { stop, ref, WritableComputedRef, - isReadonly + isReadonly, + ReactiveEffect, + ComputedRef } from '../src' describe('reactivity/computed', () => { @@ -193,4 +195,58 @@ describe('reactivity/computed', () => { expect(isReadonly(z)).toBe(false) expect(isReadonly(z.value.a)).toBe(false) }) + + describe('cleanup', () => { + let value: { foo: number } + let cValue: ComputedRef + let onTrigger: any + let effect: ReactiveEffect + beforeEach(() => { + value = reactive<{ foo: number }>({ foo: 123 }) + cValue = computed(() => value.foo) + + onTrigger = jest.fn(() => {}) + effect = cValue.effect as ReactiveEffect + }) + + it('should be dereferenced after cleanup', () => { + // Initially, no references should be present. + expect(effect.deps.length).toBe(0) + + expect(cValue.value).toBe(123) + + // Should create the dependency. + expect(effect.deps.length).toBe(1) + + const reactiveDeps = effect.deps[0] + expect(reactiveDeps).toContain(effect) + + onTrigger.mockReset() + + // After cleanup, the dependencies should be cleared. + cValue.cleanup() + + expect(reactiveDeps).not.toContain(effect) + expect(effect.deps).toEqual([]) + }) + + it('should recover when used after cleanup', () => { + expect(cValue.value).toBe(123) + + const reactiveDeps = effect.deps[0] + expect(reactiveDeps).toContain(effect) + + cValue.cleanup() + + // Should still be 'active' as it still fully functional (can recover). + expect(effect.active).toBe(true) + + value.foo = 4 + expect(cValue.value).toBe(4) + + // After invoking, should be linked again. + expect(reactiveDeps).toContain(effect) + expect(effect.deps).toEqual([reactiveDeps]) + }) + }) }) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 7f2c5b50d80..3a6c3fa1c0a 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,4 +1,4 @@ -import { effect, ReactiveEffect, trigger, track } from './effect' +import { effect, ReactiveEffect, trigger, track, cleanup } from './effect' import { TriggerOpTypes, TrackOpTypes } from './operations' import { Ref } from './ref' import { isFunction, NOOP } from '@vue/shared' @@ -6,6 +6,7 @@ import { ReactiveFlags, toRaw } from './reactive' export interface ComputedRef extends WritableComputedRef { readonly value: T + cleanup(): void } export interface WritableComputedRef extends Ref { @@ -59,6 +60,12 @@ class ComputedRefImpl { set value(newValue: T) { this._setter(newValue) } + + cleanup() { + cleanup(this.effect) + this._dirty = true + this._value = undefined! + } } export function computed(getter: ComputedGetter): ComputedRef diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 683f8fa9e3c..c91dcbfa342 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -110,7 +110,7 @@ function createReactiveEffect( return effect } -function cleanup(effect: ReactiveEffect) { +export function cleanup(effect: ReactiveEffect) { const { deps } = effect if (deps.length) { for (let i = 0; i < deps.length; i++) {