From 5cea9a1d4e846f60515ef76ebab4800228645601 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 8 Jul 2021 17:09:23 -0400 Subject: [PATCH] feat(reactivity): support onTrack/onTrigger debug options for computed --- .../reactivity/__tests__/computed.spec.ts | 76 ++++++++++++++++++- packages/reactivity/src/computed.ts | 24 ++++-- packages/reactivity/src/effect.ts | 9 ++- packages/reactivity/src/index.ts | 1 + 4 files changed, 100 insertions(+), 10 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index c4067722ed1..3f40b1c45ec 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -5,7 +5,12 @@ import { ref, WritableComputedRef, isReadonly, - setComputedScheduler + setComputedScheduler, + DebuggerEvent, + toRaw, + TrackOpTypes, + ITERATE_KEY, + TriggerOpTypes } from '../src' describe('reactivity/computed', () => { @@ -200,6 +205,75 @@ describe('reactivity/computed', () => { expect(x.value).toBe(1) }) + it('debug: onTrack', () => { + let events: DebuggerEvent[] = [] + const onTrack = jest.fn((e: DebuggerEvent) => { + events.push(e) + }) + const obj = reactive({ foo: 1, bar: 2 }) + const c = computed(() => (obj.foo, 'bar' in obj, Object.keys(obj)), { + onTrack + }) + expect(c.value).toEqual(['foo', 'bar']) + expect(onTrack).toHaveBeenCalledTimes(3) + expect(events).toEqual([ + { + effect: c.effect, + target: toRaw(obj), + type: TrackOpTypes.GET, + key: 'foo' + }, + { + effect: c.effect, + target: toRaw(obj), + type: TrackOpTypes.HAS, + key: 'bar' + }, + { + effect: c.effect, + target: toRaw(obj), + type: TrackOpTypes.ITERATE, + key: ITERATE_KEY + } + ]) + }) + + it('debug: onTrigger', () => { + let events: DebuggerEvent[] = [] + const onTrigger = jest.fn((e: DebuggerEvent) => { + events.push(e) + }) + const obj = reactive({ foo: 1 }) + const c = computed(() => obj.foo, { onTrigger }) + + // computed won't trigger compute until accessed + c.value + + obj.foo++ + expect(c.value).toBe(2) + expect(onTrigger).toHaveBeenCalledTimes(1) + expect(events[0]).toEqual({ + effect: c.effect, + target: toRaw(obj), + type: TriggerOpTypes.SET, + key: 'foo', + oldValue: 1, + newValue: 2 + }) + + // @ts-ignore + delete obj.foo + expect(c.value).toBeUndefined() + expect(onTrigger).toHaveBeenCalledTimes(2) + expect(events[1]).toEqual({ + effect: c.effect, + target: toRaw(obj), + type: TriggerOpTypes.DELETE, + key: 'foo', + oldValue: 2 + }) + }) + describe('with scheduler', () => { // a simple scheduler similar to the main Vue scheduler const tick = Promise.resolve() diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index e1800f8c153..c992031d5ff 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,4 +1,4 @@ -import { ReactiveEffect } from './effect' +import { DebuggerOptions, ReactiveEffect } from './effect' import { Ref, trackRefValue, triggerRefValue } from './ref' import { isFunction, NOOP } from '@vue/shared' import { ReactiveFlags, toRaw } from './reactive' @@ -101,12 +101,17 @@ class ComputedRefImpl { } } -export function computed(getter: ComputedGetter): ComputedRef export function computed( - options: WritableComputedOptions + getter: ComputedGetter, + debugOptions?: DebuggerOptions +): ComputedRef +export function computed( + options: WritableComputedOptions, + debugOptions?: DebuggerOptions ): WritableComputedRef export function computed( - getterOrOptions: ComputedGetter | WritableComputedOptions + getterOrOptions: ComputedGetter | WritableComputedOptions, + debugOptions?: DebuggerOptions ) { let getter: ComputedGetter let setter: ComputedSetter @@ -123,9 +128,16 @@ export function computed( setter = getterOrOptions.set } - return new ComputedRefImpl( + const cRef = new ComputedRefImpl( getter, setter, isFunction(getterOrOptions) || !getterOrOptions.set - ) as any + ) + + if (__DEV__ && debugOptions) { + cRef.effect.onTrack = debugOptions.onTrack + cRef.effect.onTrigger = debugOptions.onTrigger + } + + return cRef as any } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 5c2ca83dada..231e15e60e1 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -124,14 +124,17 @@ function cleanupEffect(effect: ReactiveEffect) { } } -export interface ReactiveEffectOptions { +export interface DebuggerOptions { + onTrack?: (event: DebuggerEvent) => void + onTrigger?: (event: DebuggerEvent) => void +} + +export interface ReactiveEffectOptions extends DebuggerOptions { lazy?: boolean scheduler?: EffectScheduler scope?: EffectScope allowRecurse?: boolean onStop?: () => void - onTrack?: (event: DebuggerEvent) => void - onTrigger?: (event: DebuggerEvent) => void } export interface ReactiveEffectRunner { diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index 3d4b05730a5..776e4cedc53 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -50,6 +50,7 @@ export { ReactiveEffectRunner, ReactiveEffectOptions, EffectScheduler, + DebuggerOptions, DebuggerEvent } from './effect' export {