Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 430cef3

Browse files
authored
perf(compiler-vapor): use onBinding helper for reactive events (#14854)
1 parent a42a8a2 commit 430cef3

8 files changed

Lines changed: 210 additions & 57 deletions

File tree

‎packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap‎

Lines changed: 25 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -36,49 +36,51 @@ export function render(_ctx) {
3636
`;
3737
3838
exports[`v-on > dynamic arg 1`] = `
39-
"import { createInvoker as _createInvoker, on as _on, renderEffect as _renderEffect, template as _template } from 'vue';
39+
"import { createInvoker as _createInvoker, onBinding as _onBinding, renderEffect as _renderEffect, template as _template } from 'vue';
4040
const t0 = _template("<div>", 1)
4141
4242
export function render(_ctx) {
4343
const n0 = t0()
44-
_renderEffect(() => {
45-
46-
_on(n0, _ctx.event, _createInvoker(e => _ctx.handler(e)), {
47-
effect: true
48-
})
49-
})
44+
_renderEffect(() => _onBinding(n0, _ctx.event, _createInvoker(e => _ctx.handler(e))))
5045
return n0
5146
}"
5247
`;
5348
5449
exports[`v-on > dynamic arg with complex exp prefixing 1`] = `
55-
"import { createInvoker as _createInvoker, on as _on, renderEffect as _renderEffect, template as _template } from 'vue';
50+
"import { createInvoker as _createInvoker, onBinding as _onBinding, renderEffect as _renderEffect, template as _template } from 'vue';
51+
const t0 = _template("<div>", 1)
52+
53+
export function render(_ctx) {
54+
const n0 = t0()
55+
_renderEffect(() => _onBinding(n0, _ctx.event(_ctx.foo), _createInvoker(e => _ctx.handler(e))))
56+
return n0
57+
}"
58+
`;
59+
60+
exports[`v-on > dynamic arg with event options 1`] = `
61+
"import { createInvoker as _createInvoker, onBinding as _onBinding, renderEffect as _renderEffect, template as _template } from 'vue';
5662
const t0 = _template("<div>", 1)
5763
5864
export function render(_ctx) {
5965
const n0 = t0()
6066
_renderEffect(() => {
6167
62-
_on(n0, _ctx.event(_ctx.foo), _createInvoker(e => _ctx.handler(e)), {
63-
effect: true
68+
_onBinding(n0, _ctx.event, _createInvoker(e => _ctx.handler(e)), {
69+
capture: true,
70+
once: true
6471
})
6572
})
6673
return n0
6774
}"
6875
`;
6976
7077
exports[`v-on > dynamic arg with prefixing 1`] = `
71-
"import { createInvoker as _createInvoker, on as _on, renderEffect as _renderEffect, template as _template } from 'vue';
78+
"import { createInvoker as _createInvoker, onBinding as _onBinding, renderEffect as _renderEffect, template as _template } from 'vue';
7279
const t0 = _template("<div>", 1)
7380
7481
export function render(_ctx) {
7582
const n0 = t0()
76-
_renderEffect(() => {
77-
78-
_on(n0, _ctx.event, _createInvoker(e => _ctx.handler(e)), {
79-
effect: true
80-
})
81-
})
83+
_renderEffect(() => _onBinding(n0, _ctx.event, _createInvoker(e => _ctx.handler(e))))
8284
return n0
8385
}"
8486
`;
@@ -385,17 +387,12 @@ export function render(_ctx) {
385387
`;
386388
387389
exports[`v-on > should transform click.middle 2`] = `
388-
"import { createInvoker as _createInvoker, withModifiers as _withModifiers, on as _on, renderEffect as _renderEffect, template as _template } from 'vue';
390+
"import { createInvoker as _createInvoker, withModifiers as _withModifiers, onBinding as _onBinding, renderEffect as _renderEffect, template as _template } from 'vue';
389391
const t0 = _template("<div>", 1)
390392
391393
export function render(_ctx) {
392394
const n0 = t0()
393-
_renderEffect(() => {
394-
395-
_on(n0, (_ctx.event) === "click" ? "mouseup" : (_ctx.event), _createInvoker(_withModifiers(e => _ctx.test(e), ["middle"])), {
396-
effect: true
397-
})
398-
})
395+
_renderEffect(() => _onBinding(n0, (_ctx.event) === "click" ? "mouseup" : (_ctx.event), _createInvoker(_withModifiers(e => _ctx.test(e), ["middle"]))))
399396
return n0
400397
}"
401398
`;
@@ -413,17 +410,12 @@ export function render(_ctx) {
413410
`;
414411
415412
exports[`v-on > should transform click.right 2`] = `
416-
"import { createInvoker as _createInvoker, withModifiers as _withModifiers, withKeys as _withKeys, on as _on, renderEffect as _renderEffect, template as _template } from 'vue';
413+
"import { createInvoker as _createInvoker, withModifiers as _withModifiers, withKeys as _withKeys, onBinding as _onBinding, renderEffect as _renderEffect, template as _template } from 'vue';
417414
const t0 = _template("<div>", 1)
418415
419416
export function render(_ctx) {
420417
const n0 = t0()
421-
_renderEffect(() => {
422-
423-
_on(n0, (_ctx.event) === "click" ? "contextmenu" : (_ctx.event), _createInvoker(_withKeys(_withModifiers(e => _ctx.test(e), ["right"]), ["right"])), {
424-
effect: true
425-
})
426-
})
418+
_renderEffect(() => _onBinding(n0, (_ctx.event) === "click" ? "contextmenu" : (_ctx.event), _createInvoker(_withKeys(_withModifiers(e => _ctx.test(e), ["right"]), ["right"]))))
427419
return n0
428420
}"
429421
`;
@@ -441,17 +433,12 @@ export function render(_ctx) {
441433
`;
442434
443435
exports[`v-on > should wrap both for dynamic key event w/ left/right modifiers 1`] = `
444-
"import { createInvoker as _createInvoker, withModifiers as _withModifiers, withKeys as _withKeys, on as _on, renderEffect as _renderEffect, template as _template } from 'vue';
436+
"import { createInvoker as _createInvoker, withModifiers as _withModifiers, withKeys as _withKeys, onBinding as _onBinding, renderEffect as _renderEffect, template as _template } from 'vue';
445437
const t0 = _template("<div>", 1)
446438
447439
export function render(_ctx) {
448440
const n0 = t0()
449-
_renderEffect(() => {
450-
451-
_on(n0, _ctx.e, _createInvoker(_withKeys(_withModifiers(e => _ctx.test(e), ["left"]), ["left"])), {
452-
effect: true
453-
})
454-
})
441+
_renderEffect(() => _onBinding(n0, _ctx.e, _createInvoker(_withKeys(_withModifiers(e => _ctx.test(e), ["left"]), ["left"]))))
455442
return n0
456443
}"
457444
`;

‎packages/compiler-vapor/__tests__/transforms/vOn.spec.ts‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ describe('v-on', () => {
8787
`<div v-on:[event]="handler"/>`,
8888
)
8989

90-
expect(helpers).contains('on')
90+
expect(helpers).contains('onBinding')
9191
expect(helpers).contains('renderEffect')
9292
expect(ir.block.operation).toMatchObject([])
9393

@@ -107,6 +107,9 @@ describe('v-on', () => {
107107
})
108108

109109
expect(code).matchSnapshot()
110+
expect(code).contains(
111+
`_onBinding(n0, _ctx.event, _createInvoker(e => _ctx.handler(e)))`,
112+
)
110113
})
111114

112115
test('dynamic arg with prefixing', () => {
@@ -117,6 +120,24 @@ describe('v-on', () => {
117120
expect(code).matchSnapshot()
118121
})
119122

123+
test('dynamic arg with event options', () => {
124+
const { code, helpers } = compileWithVOn(
125+
`<div v-on:[event].capture.once="handler"/>`,
126+
{
127+
prefixIdentifiers: true,
128+
},
129+
)
130+
131+
expect(helpers).contains('onBinding')
132+
expect(code).matchSnapshot()
133+
expect(code).contains(
134+
`_onBinding(n0, _ctx.event, _createInvoker(e => _ctx.handler(e)), {`,
135+
)
136+
expect(code).contains('capture: true')
137+
expect(code).contains('once: true')
138+
expect(code).not.contains('effect: true')
139+
})
140+
120141
test('dynamic arg with complex exp prefixing', () => {
121142
const { ir, code, helpers } = compileWithVOn(
122143
`<div v-on:[event(foo)]="handler"/>`,
@@ -125,7 +146,7 @@ describe('v-on', () => {
125146
},
126147
)
127148

128-
expect(helpers).contains('on')
149+
expect(helpers).contains('onBinding')
129150
expect(helpers).contains('renderEffect')
130151
expect(ir.block.operation).toMatchObject([])
131152

‎packages/compiler-vapor/src/generators/event.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function genSetEvent(
5050
return [
5151
NEWLINE,
5252
...genCall(
53-
helper(delegate ? 'delegate' : 'on'),
53+
helper(effect ? 'onBinding' : delegate ? 'delegate' : 'on'),
5454
`n${element}`,
5555
name,
5656
handler,
@@ -73,11 +73,10 @@ export function genSetEvent(
7373

7474
function genEventOptions(): CodeFragment[] | undefined {
7575
let { options } = modifiers
76-
if (!options.length && !effect) return
76+
if (!options.length) return
7777

7878
return genMulti(
7979
DELIMITERS_OBJECT_NEWLINE,
80-
effect && ['effect: true'],
8180
...options.map((option): CodeFragment[] => [`${option}: true`]),
8281
)
8382
}

‎packages/runtime-vapor/__tests__/dom/event.spec.ts‎

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { effectScope } from '@vue/reactivity'
1+
import { effectScope, ref } from '@vue/reactivity'
2+
import { nextTick } from '@vue/runtime-dom'
23
import {
34
delegate,
45
delegateEvents,
56
on,
7+
onBinding,
68
renderEffect,
79
setDynamicEvents,
810
} from '../../src'
@@ -18,6 +20,56 @@ describe('dom event', () => {
1820
expect(handler).toHaveBeenCalled()
1921
})
2022

23+
test('onBinding', () => {
24+
const el = document.createElement('div')
25+
const handler = vi.fn()
26+
const scope = effectScope()
27+
scope.run(() => {
28+
renderEffect(() => {
29+
onBinding(el, 'click', handler)
30+
})
31+
})
32+
el.click()
33+
expect(handler).toHaveBeenCalledTimes(1)
34+
scope.stop()
35+
el.click()
36+
expect(handler).toHaveBeenCalledTimes(1)
37+
})
38+
39+
test('onBinding cleans previous listener on update', async () => {
40+
const el = document.createElement('div')
41+
const event = ref('click')
42+
const clickHandler = vi.fn()
43+
const mouseupHandler = vi.fn()
44+
const scope = effectScope()
45+
scope.run(() => {
46+
renderEffect(() => {
47+
onBinding(
48+
el,
49+
event.value,
50+
event.value === 'click' ? clickHandler : mouseupHandler,
51+
)
52+
})
53+
})
54+
55+
el.click()
56+
expect(clickHandler).toHaveBeenCalledTimes(1)
57+
expect(mouseupHandler).not.toHaveBeenCalled()
58+
59+
event.value = 'mouseup'
60+
await nextTick()
61+
62+
el.click()
63+
expect(clickHandler).toHaveBeenCalledTimes(1)
64+
65+
el.dispatchEvent(new MouseEvent('mouseup'))
66+
expect(mouseupHandler).toHaveBeenCalledTimes(1)
67+
68+
scope.stop()
69+
el.dispatchEvent(new MouseEvent('mouseup'))
70+
expect(mouseupHandler).toHaveBeenCalledTimes(1)
71+
})
72+
2173
test('delegate with direct attachment', () => {
2274
const el = document.createElement('div')
2375
document.body.appendChild(el)
@@ -100,7 +152,49 @@ describe('dom event', () => {
100152
})
101153
})
102154
el.click()
103-
expect(handler).toHaveBeenCalled()
155+
expect(handler).toHaveBeenCalledTimes(1)
156+
scope.stop()
157+
el.click()
158+
expect(handler).toHaveBeenCalledTimes(1)
159+
})
160+
161+
test('setDynamicEvents with multiple handlers', () => {
162+
const el = document.createElement('div')
163+
const handler1 = vi.fn()
164+
const handler2 = vi.fn()
165+
const scope = effectScope()
166+
scope.run(() => {
167+
renderEffect(() => {
168+
setDynamicEvents(el, {
169+
click: [handler1, handler2],
170+
})
171+
})
172+
})
173+
174+
el.click()
175+
expect(handler1).toHaveBeenCalledTimes(1)
176+
expect(handler2).toHaveBeenCalledTimes(1)
177+
178+
scope.stop()
179+
el.click()
180+
expect(handler1).toHaveBeenCalledTimes(1)
181+
expect(handler2).toHaveBeenCalledTimes(1)
182+
})
183+
184+
test('setDynamicEvents accepts narrowed event handlers', () => {
185+
const el = document.createElement('div')
186+
const handler = vi.fn()
187+
const scope = effectScope()
188+
scope.run(() => {
189+
renderEffect(() => {
190+
setDynamicEvents(el, {
191+
click: (e: MouseEvent) => handler(e.clientX),
192+
})
193+
})
194+
})
195+
196+
el.click()
197+
expect(handler).toHaveBeenCalledTimes(1)
104198
scope.stop()
105199
})
106200
})

‎packages/runtime-vapor/__tests__/dom/prop.spec.ts‎

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@ import {
2020
createComponent,
2121
isApplyingFallthroughProps,
2222
} from '../../src/component'
23-
import { ref, setCurrentInstance, svgNS, xlinkNS } from '@vue/runtime-dom'
23+
import {
24+
effectScope,
25+
nextTick,
26+
ref,
27+
setCurrentInstance,
28+
svgNS,
29+
xlinkNS,
30+
} from '@vue/runtime-dom'
2431
import { makeRender } from '../_utils'
2532
import {
2633
createDynamicComponent,
@@ -658,6 +665,35 @@ describe('patchProp', () => {
658665
expect(fallthroughSetCount).toBe(2)
659666
})
660667

668+
test('should clean dynamic event props on effect update and stop', async () => {
669+
const el = document.createElement('button')
670+
const active = ref(true)
671+
const handler = vi.fn()
672+
const scope = effectScope()
673+
scope.run(() => {
674+
renderEffect(() => {
675+
setDynamicProps(el, [active.value ? { onClick: handler } : {}])
676+
})
677+
})
678+
679+
el.click()
680+
expect(handler).toHaveBeenCalledTimes(1)
681+
682+
active.value = false
683+
await nextTick()
684+
el.click()
685+
expect(handler).toHaveBeenCalledTimes(1)
686+
687+
active.value = true
688+
await nextTick()
689+
el.click()
690+
expect(handler).toHaveBeenCalledTimes(2)
691+
692+
scope.stop()
693+
el.click()
694+
expect(handler).toHaveBeenCalledTimes(2)
695+
})
696+
661697
test('should restore fallthrough state when dynamic props throw', () => {
662698
const el = document.createElement('div')
663699
const attrs: Record<string, any> = {}

0 commit comments

Comments
 (0)