Skip to content

Commit dae380d

Browse files
committed
wip: renderTriggered/renderTracked
1 parent 075f215 commit dae380d

File tree

6 files changed

+150
-22
lines changed

6 files changed

+150
-22
lines changed

src/core/instance/lifecycle.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import config from '../config'
2-
import Watcher from '../observer/watcher'
2+
import Watcher, { WatcherOptions } from '../observer/watcher'
33
import { mark, measure } from '../util/perf'
44
import VNode, { createEmptyVNode } from '../vdom/vnode'
55
import { updateComponentListeners } from './events'
@@ -192,20 +192,27 @@ export function mountComponent(
192192
}
193193
}
194194

195+
const watcherOptions: WatcherOptions = {
196+
before() {
197+
if (vm._isMounted && !vm._isDestroyed) {
198+
callHook(vm, 'beforeUpdate')
199+
}
200+
}
201+
}
202+
203+
if (__DEV__) {
204+
watcherOptions.onTrack = e => callHook(vm, 'renderTracked', [e])
205+
watcherOptions.onTrigger = e => callHook(vm, 'renderTriggered', [e])
206+
}
207+
195208
// we set this to vm._watcher inside the watcher's constructor
196209
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
197210
// component's mounted hook), which relies on vm._watcher being already defined
198211
new Watcher(
199212
vm,
200213
updateComponent,
201214
noop,
202-
{
203-
before() {
204-
if (vm._isMounted && !vm._isDestroyed) {
205-
callHook(vm, 'beforeUpdate')
206-
}
207-
}
208-
},
215+
watcherOptions,
209216
true /* isRenderWatcher */
210217
)
211218
hydrating = false
@@ -365,7 +372,7 @@ export function deactivateChildComponent(vm: Component, direct?: boolean) {
365372
}
366373
}
367374

368-
export function callHook(vm: Component, hook: string) {
375+
export function callHook(vm: Component, hook: string, args?: any[]) {
369376
// #7573 disable dep collection when invoking lifecycle hooks
370377
pushTarget()
371378
const prev = currentInstance
@@ -374,7 +381,7 @@ export function callHook(vm: Component, hook: string) {
374381
const info = `${hook} hook`
375382
if (handlers) {
376383
for (let i = 0, j = handlers.length; i < j; i++) {
377-
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
384+
invokeWithErrorHandling(handlers[i], vm, args || null, vm, info)
378385
}
379386
}
380387
if (vm._hasHookEvent) {

src/core/observer/watcher.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ import {
1212

1313
import { traverse } from './traverse'
1414
import { queueWatcher } from './scheduler'
15-
import Dep, { pushTarget, popTarget, DepTarget, DebuggerEvent } from './dep'
15+
import Dep, {
16+
pushTarget,
17+
popTarget,
18+
DepTarget,
19+
DebuggerEvent,
20+
DebuggerOptions
21+
} from './dep'
1622

1723
import type { SimpleSet } from '../util/index'
1824
import type { Component } from 'typescript/component'
@@ -23,6 +29,14 @@ import {
2329

2430
let uid = 0
2531

32+
export interface WatcherOptions extends DebuggerOptions {
33+
deep?: boolean
34+
user?: boolean
35+
lazy?: boolean
36+
sync?: boolean
37+
before?: Function
38+
}
39+
2640
/**
2741
* A watcher parses an expression, collects dependencies,
2842
* and fires callback when the expression value changes.
@@ -57,14 +71,7 @@ export default class Watcher implements DepTarget {
5771
vm: Component | null,
5872
expOrFn: string | (() => any),
5973
cb: Function,
60-
options?: {
61-
deep?: boolean
62-
user?: boolean
63-
lazy?: boolean
64-
sync?: boolean
65-
before?: Function
66-
scheduler?: Function
67-
} | null,
74+
options?: WatcherOptions | null,
6875
isRenderWatcher?: boolean
6976
) {
7077
recordEffectScope(this, activeEffectScope || (vm ? vm._scope : undefined))
@@ -80,6 +87,10 @@ export default class Watcher implements DepTarget {
8087
this.lazy = !!options.lazy
8188
this.sync = !!options.sync
8289
this.before = options.before
90+
if (__DEV__) {
91+
this.onTrack = options.onTrack
92+
this.onTrigger = options.onTrigger
93+
}
8394
} else {
8495
this.deep = this.user = this.lazy = this.sync = false
8596
}

src/v3/apiLifecycle.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import { DebuggerEvent } from '.'
12
import { Component } from '../../typescript/component'
23
import { mergeLifecycleHook, warn } from '../core/util'
34
import { currentInstance } from './currentInstance'
45

5-
function createLifeCycle(hookName: string) {
6-
return (fn: () => void, target: Component | null = currentInstance) => {
6+
function createLifeCycle<T extends (...args: any[]) => any = () => void>(
7+
hookName: string
8+
) {
9+
return (fn: T, target: Component | null = currentInstance) => {
710
if (!target) {
811
__DEV__ &&
912
warn(
@@ -43,3 +46,8 @@ export const onErrorCaptured = createLifeCycle('errorCaptured')
4346
export const onActivated = createLifeCycle('activated')
4447
export const onDeactivated = createLifeCycle('deactivated')
4548
export const onServerPrefetch = createLifeCycle('serverPrefetch')
49+
50+
export const onRenderTracked =
51+
createLifeCycle<(e: DebuggerEvent) => any>('renderTracked')
52+
export const onRenderTriggered =
53+
createLifeCycle<(e: DebuggerEvent) => any>('renderTriggered')

test/unit/features/v3/apiLifecycle.spec.ts

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ import {
44
onBeforeMount,
55
onMounted,
66
ref,
7+
reactive,
78
onBeforeUpdate,
89
onUpdated,
910
onBeforeUnmount,
10-
onUnmounted
11+
onUnmounted,
12+
onRenderTracked,
13+
onRenderTriggered,
14+
DebuggerEvent,
15+
TrackOpTypes,
16+
TriggerOpTypes
1117
} from 'v3'
1218
import { nextTick } from 'core/util'
1319

@@ -281,4 +287,74 @@ describe('api: lifecycle hooks', () => {
281287
'root onUnmounted'
282288
])
283289
})
290+
291+
it('onRenderTracked', () => {
292+
const events: DebuggerEvent[] = []
293+
const onTrack = vi.fn((e: DebuggerEvent) => {
294+
events.push(e)
295+
})
296+
const obj = reactive({ foo: 1, bar: 2 })
297+
298+
const Comp = {
299+
setup() {
300+
onRenderTracked(onTrack)
301+
return () => h('div', [obj.foo + obj.bar])
302+
}
303+
}
304+
305+
new Vue(Comp).$mount()
306+
expect(onTrack).toHaveBeenCalledTimes(2)
307+
expect(events).toMatchObject([
308+
{
309+
target: obj,
310+
type: TrackOpTypes.GET,
311+
key: 'foo'
312+
},
313+
{
314+
target: obj,
315+
type: TrackOpTypes.GET,
316+
key: 'bar'
317+
}
318+
])
319+
})
320+
321+
it('onRenderTriggered', async () => {
322+
const events: DebuggerEvent[] = []
323+
const onTrigger = vi.fn((e: DebuggerEvent) => {
324+
events.push(e)
325+
})
326+
const obj = reactive<{
327+
foo: number
328+
bar: number
329+
}>({ foo: 1, bar: 2 })
330+
331+
const Comp = {
332+
setup() {
333+
onRenderTriggered(onTrigger)
334+
return () => h('div', [obj.foo + obj.bar])
335+
}
336+
}
337+
338+
new Vue(Comp).$mount()
339+
340+
obj.foo++
341+
await nextTick()
342+
expect(onTrigger).toHaveBeenCalledTimes(1)
343+
expect(events[0]).toMatchObject({
344+
type: TriggerOpTypes.SET,
345+
key: 'foo',
346+
oldValue: 1,
347+
newValue: 2
348+
})
349+
350+
obj.bar++
351+
await nextTick()
352+
expect(onTrigger).toHaveBeenCalledTimes(2)
353+
expect(events[1]).toMatchObject({
354+
type: TriggerOpTypes.SET,
355+
key: 'bar',
356+
oldValue: 2,
357+
newValue: 3
358+
})
359+
})
284360
})

types/options.d.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ export interface ComponentOptions<
189189
deactivated?(): void
190190
errorCaptured?(err: Error, vm: Vue, info: string): boolean | void
191191
serverPrefetch?(this: V): Promise<void>
192+
renderTracked?(e: DebuggerEvent): void
193+
renderTriggerd?(e: DebuggerEvent): void
192194

193195
directives?: { [key: string]: DirectiveFunction | DirectiveOptions }
194196
components?: {
@@ -314,3 +316,24 @@ export type InjectOptions =
314316
[key: string]: InjectKey | { from?: InjectKey; default?: any }
315317
}
316318
| string[]
319+
320+
export type DebuggerEvent = {
321+
target: object
322+
type: TrackOpTypes | TriggerOpTypes
323+
key?: any
324+
newValue?: any
325+
oldValue?: any
326+
oldTarget?: Map<any, any> | Set<any>
327+
}
328+
329+
export const enum TrackOpTypes {
330+
GET = 'get',
331+
TOUCH = 'touch'
332+
}
333+
334+
export const enum TriggerOpTypes {
335+
SET = 'set',
336+
ADD = 'add',
337+
DELETE = 'delete',
338+
ARRAY_MUTATION = 'array mutation'
339+
}

typescript/options.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import VNode from '../src/core/vdom/vnode'
2+
import { DebuggerEvent } from '../src/v3'
23
import { Component } from './component'
34

45
declare type InternalComponentOptions = {
@@ -61,6 +62,8 @@ declare type ComponentOptions = {
6162
destroyed?: Function
6263
errorCaptured?: () => boolean | void
6364
serverPrefetch?: Function
65+
renderTracked?(e: DebuggerEvent): void
66+
renderTriggerd?(e: DebuggerEvent): void
6467

6568
// assets
6669
directives?: { [key: string]: object }

0 commit comments

Comments
 (0)