wip: onTrack debugger option

This commit is contained in:
Evan You 2022-05-29 15:05:35 +08:00
parent 9fb4f7d070
commit 15c5c43ca6
13 changed files with 176 additions and 121 deletions

View File

@ -30,6 +30,7 @@ import {
isFunction
} from '../util/index'
import type { Component } from 'typescript/component'
import { TrackOpTypes } from '../../v3'
const sharedPropertyDefinition = {
enumerable: true,
@ -254,6 +255,14 @@ function createComputedGetter(key) {
watcher.evaluate()
}
if (Dep.target) {
if (__DEV__ && Dep.target.onTrack) {
Dep.target.onTrack({
effect: Dep.target,
target: this,
type: TrackOpTypes.GET,
key
})
}
watcher.depend()
}
return watcher.value

View File

@ -1,14 +1,33 @@
import { remove } from '../util/index'
import config from '../config'
import { TrackOpTypes, TriggerOpTypes } from 'v3'
let uid = 0
export interface DepTarget {
export interface DepTarget extends DebuggerOptions {
id: number
addDep(dep: Dep): void
update(): void
}
export interface DebuggerOptions {
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
}
export type DebuggerEvent = {
effect: DepTarget
} & DebuggerEventExtraInfo
export type DebuggerEventExtraInfo = {
target: object
type: TrackOpTypes | TriggerOpTypes
key?: any
newValue?: any
oldValue?: any
oldTarget?: Map<any, any> | Set<any>
}
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
@ -31,13 +50,19 @@ export default class Dep {
remove(this.subs, sub)
}
depend() {
depend(info?: DebuggerEventExtraInfo) {
if (Dep.target) {
Dep.target.addDep(this)
if (__DEV__ && info && Dep.target.onTrack) {
Dep.target.onTrack({
effect: Dep.target,
...info
})
}
}
}
notify() {
notify(info?: DebuggerEventExtraInfo) {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (__DEV__ && !config.async) {
@ -47,6 +72,14 @@ export default class Dep {
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
if (__DEV__ && info) {
const sub = subs[i]
sub.onTrigger &&
sub.onTrigger({
effect: subs[i],
...info
})
}
subs[i].update()
}
}

View File

@ -15,7 +15,7 @@ import {
isServerRendering,
hasChanged
} from '../util/index'
import { isReadonly, isRef } from '../../v3'
import { isReadonly, isRef, TrackOpTypes } from '../../v3'
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
@ -165,7 +165,15 @@ export function defineReactive(
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (__DEV__) {
dep.depend({
target: obj,
type: TrackOpTypes.GET,
key
})
} else {
dep.depend()
}
if (childOb) {
childOb.dep.depend()
if (isArray(value)) {
@ -291,7 +299,9 @@ export function del(target: Array<any> | Object, key: any) {
function dependArray(value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
if (e && e.__ob__) {
e.__ob__.dep.depend()
}
if (isArray(e)) {
dependArray(e)
}

View File

@ -12,7 +12,7 @@ import {
import { traverse } from './traverse'
import { queueWatcher } from './scheduler'
import Dep, { pushTarget, popTarget, DepTarget } from './dep'
import Dep, { pushTarget, popTarget, DepTarget, DebuggerEvent } from './dep'
import type { SimpleSet } from '../util/index'
import type { Component } from 'typescript/component'
@ -49,6 +49,10 @@ export default class Watcher implements DepTarget {
getter: Function
value: any
// dev only
onTrack?: ((event: DebuggerEvent) => void) | undefined
onTrigger?: ((event: DebuggerEvent) => void) | undefined
constructor(
vm: Component | null,
expOrFn: string | (() => any),

View File

@ -15,7 +15,6 @@ if (__DEV__) {
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
warn = (msg, vm = currentInstance) => {
// TODO get current instance
const trace = vm ? generateComponentTrace(vm) : ''
if (config.warnHandler) {

View File

@ -15,7 +15,7 @@ import { currentInstance } from './currentInstance'
import { traverse } from 'core/observer/traverse'
import Watcher from '../core/observer/watcher'
import { queueWatcher } from '../core/observer/scheduler'
import { TrackOpTypes, TriggerOpTypes } from './reactivity/operations'
import { DebuggerOptions } from '../core/observer/dep'
const WATCHER = `watcher`
const WATCHER_CB = `${WATCHER} callback`
@ -50,24 +50,6 @@ export interface WatchOptionsBase extends DebuggerOptions {
flush?: 'pre' | 'post' | 'sync'
}
export interface DebuggerOptions {
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
}
export type DebuggerEvent = {
watcher: Watcher
} & DebuggerEventExtraInfo
export type DebuggerEventExtraInfo = {
target: object
type: TrackOpTypes | TriggerOpTypes
key: any
newValue?: any
oldValue?: any
oldTarget?: Map<any, any> | Set<any>
}
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
immediate?: Immediate
deep?: boolean
@ -349,11 +331,10 @@ function doWatch(
}
}
// TODO
// if (__DEV__) {
// effect.onTrack = onTrack
// effect.onTrigger = onTrigger
// }
if (__DEV__) {
watcher.onTrack = onTrack
watcher.onTrigger = onTrigger
}
// initial run
if (cb) {
@ -370,9 +351,5 @@ function doWatch(
return () => {
watcher.teardown()
// TODO
// if (instance && instance.scope) {
// remove(instance.scope.effects!, effect)
// }
}
}

View File

@ -52,9 +52,7 @@ export {
WatchOptionsBase,
WatchCallback,
WatchSource,
WatchStopHandle,
DebuggerOptions,
DebuggerEvent
WatchStopHandle
} from './apiWatch'
export {
@ -64,6 +62,11 @@ export {
getCurrentScope
} from './reactivity/effectScope'
export {
DebuggerOptions,
DebuggerEvent,
DebuggerEventExtraInfo
} from 'core/observer/dep'
export { TrackOpTypes, TriggerOpTypes } from './reactivity/operations'
export { h } from './h'

View File

@ -1,10 +1,10 @@
import { isServerRendering, noop, warn, def, isFunction } from 'core/util'
import { Ref, RefFlag } from './ref'
import Watcher from 'core/observer/watcher'
import Dep from 'core/observer/dep'
import Dep, { DebuggerOptions } from 'core/observer/dep'
import { currentInstance } from '../currentInstance'
import { DebuggerOptions } from '../apiWatch'
import { ReactiveFlags } from './reactive'
import { TrackOpTypes } from './operations'
declare const ComputedRefSymbol: unique symbol
@ -35,7 +35,6 @@ export function computed<T>(
): WritableComputedRef<T>
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
// TODO debug options
debugOptions?: DebuggerOptions
) {
let getter: ComputedGetter<T>
@ -58,6 +57,11 @@ export function computed<T>(
? null
: new Watcher(currentInstance, getter, noop, { lazy: true })
if (__DEV__ && watcher && debugOptions) {
watcher.onTrack = debugOptions.onTrack
watcher.onTrigger = debugOptions.onTrigger
}
const ref = {
// some libs rely on the presence effect for checking computed refs
// from normal refs, but the implementation doesn't matter
@ -68,6 +72,14 @@ export function computed<T>(
watcher.evaluate()
}
if (Dep.target) {
if (__DEV__ && Dep.target.onTrack) {
Dep.target.onTrack({
effect: Dep.target,
target: ref,
type: TrackOpTypes.GET,
key: 'value'
})
}
watcher.depend()
}
return watcher.value

View File

@ -3,13 +3,11 @@
export const enum TrackOpTypes {
GET = 'get',
HAS = 'has',
ITERATE = 'iterate'
TOUCH = 'touch'
}
export const enum TriggerOpTypes {
SET = 'set',
ADD = 'add',
DELETE = 'delete',
CLEAR = 'clear'
DELETE = 'delete'
}

View File

@ -93,7 +93,6 @@ export function isShallow(value: unknown): boolean {
}
export function isReadonly(value: unknown): boolean {
// TODO
return !!(value && (value as Target).__v_isReadonly)
}

View File

@ -7,6 +7,7 @@ import {
import type { IfAny } from 'typescript/utils'
import Dep from 'core/observer/dep'
import { warn, isArray, def } from 'core/util'
import { TrackOpTypes } from './operations'
declare const RefSymbol: unique symbol
export declare const RawSymbol: unique symbol
@ -91,8 +92,20 @@ export type CustomRefFactory<T> = (
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
const dep = new Dep()
const { get, set } = factory(
() => dep.depend(),
() => dep.notify()
() => {
if (__DEV__) {
dep.depend({
target: ref,
type: TrackOpTypes.GET,
key: 'value'
})
} else {
dep.depend()
}
},
() => {
dep.notify()
}
)
const ref = {
get value() {

View File

@ -12,7 +12,10 @@ import {
h,
onMounted,
getCurrentInstance,
effectScope
effectScope,
TrackOpTypes,
TriggerOpTypes,
DebuggerEvent
} from 'v3'
import { nextTick } from 'core/util'
import { set } from 'core/observer'
@ -777,41 +780,43 @@ describe('api: watch', () => {
expect(`"deep" option is only respected`).toHaveBeenWarned()
})
// TODO
// it('onTrack', async () => {
// const events: DebuggerEvent[] = []
// let dummy
// const onTrack = vi.fn((e: DebuggerEvent) => {
// events.push(e)
// })
// const obj = reactive({ foo: 1, bar: 2 })
// watchEffect(
// () => {
// dummy = [obj.foo, 'bar' in obj, Object.keys(obj)]
// },
// { onTrack }
// )
// await nextTick()
// expect(dummy).toEqual([1, true, ['foo', 'bar']])
// expect(onTrack).toHaveBeenCalledTimes(3)
// expect(events).toMatchObject([
// {
// target: obj,
// type: TrackOpTypes.GET,
// key: 'foo'
// },
// {
// target: obj,
// type: TrackOpTypes.HAS,
// key: 'bar'
// },
// {
// target: obj,
// type: TrackOpTypes.ITERATE,
// key: ITERATE_KEY
// }
// ])
// })
it('onTrack', async () => {
const events: DebuggerEvent[] = []
let dummy
const onTrack = vi.fn((e: DebuggerEvent) => {
events.push(e)
})
const obj = reactive({ foo: 1 })
const r = ref(2)
const c = computed(() => r.value + 1)
// TODO computed & ref
watchEffect(
() => {
dummy = obj.foo + r.value + c.value
},
{ onTrack }
)
await nextTick()
expect(dummy).toEqual(6)
expect(onTrack).toHaveBeenCalledTimes(3)
expect(events).toMatchObject([
{
target: obj,
type: TrackOpTypes.GET,
key: 'foo'
},
{
target: r,
type: TrackOpTypes.GET,
key: 'value'
},
{
target: c,
type: TrackOpTypes.GET,
key: 'value'
}
])
})
// it('onTrigger', async () => {
// const events: DebuggerEvent[] = []

View File

@ -3,9 +3,9 @@ import {
reactive,
ref,
isReadonly,
// toRaw,
WritableComputedRef
// DebuggerEvent
WritableComputedRef,
DebuggerEvent,
TrackOpTypes
} from 'v3'
import { effect } from 'v3/reactivity/effect'
import { nextTick } from 'core/util'
@ -225,39 +225,32 @@ describe('reactivity/computed', () => {
expect(x.value).toBe(1)
})
// TODO
// it('debug: onTrack', () => {
// let events: DebuggerEvent[] = []
// const onTrack = vi.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: onTrack', () => {
let events: DebuggerEvent[] = []
const onTrack = vi.fn((e: DebuggerEvent) => {
events.push(e)
})
const obj = reactive({ foo: 1, bar: 2 })
const c = computed(() => obj.foo + obj.bar, {
onTrack
})
expect(c.value).toEqual(3)
expect(onTrack).toHaveBeenCalledTimes(2)
expect(events).toEqual([
{
effect: c.effect,
target: obj,
type: TrackOpTypes.GET,
key: 'foo'
},
{
effect: c.effect,
target: obj,
type: TrackOpTypes.GET,
key: 'bar'
}
])
})
// TODO
// it('debug: onTrigger', () => {