Skip to content

Commit 55566e8

Browse files
committed
refactor(runtime-core): remove emit return value
BREAKING CHANGE: this.$emit() and setupContext.emit() no longer return values. For logic that relies on return value of listeners, the listener should be declared as an `onXXX` prop and be called directly. This still allows the parent component to pass in a handler using `v-on`, since `v-on:foo` internally compiles to `onFoo`. ref: vuejs/rfcs#16
1 parent a6e2b10 commit 55566e8

File tree

5 files changed

+95
-166
lines changed

5 files changed

+95
-166
lines changed

packages/runtime-core/__tests__/component.spec.ts

-146
This file was deleted.

packages/runtime-core/__tests__/componentEmits.spec.ts

+29-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { mockWarn } from '@vue/shared'
55
import { render, defineComponent, h, nodeOps } from '@vue/runtime-test'
66
import { isEmitListener } from '../src/componentEmits'
77

8-
describe('emits option', () => {
8+
describe('component: emit', () => {
99
mockWarn()
1010

1111
test('trigger both raw event and capitalize handlers', () => {
@@ -27,6 +27,7 @@ describe('emits option', () => {
2727
expect(onBar).toHaveBeenCalled()
2828
})
2929

30+
// for v-model:foo-bar usage in DOM templates
3031
test('trigger hyphendated events for update:xxx events', () => {
3132
const Foo = defineComponent({
3233
render() {},
@@ -49,6 +50,33 @@ describe('emits option', () => {
4950
expect(barSpy).toHaveBeenCalled()
5051
})
5152

53+
test('should trigger array of listeners', async () => {
54+
const Child = defineComponent({
55+
setup(_, { emit }) {
56+
emit('foo', 1)
57+
return () => h('div')
58+
}
59+
})
60+
61+
const fn1 = jest.fn()
62+
const fn2 = jest.fn()
63+
64+
const App = {
65+
setup() {
66+
return () =>
67+
h(Child, {
68+
onFoo: [fn1, fn2]
69+
})
70+
}
71+
}
72+
73+
render(h(App), nodeOps.createElement('div'))
74+
expect(fn1).toHaveBeenCalledTimes(1)
75+
expect(fn1).toHaveBeenCalledWith(1)
76+
expect(fn2).toHaveBeenCalledTimes(1)
77+
expect(fn1).toHaveBeenCalledWith(1)
78+
})
79+
5280
test('warning for undeclared event (array)', () => {
5381
const Foo = defineComponent({
5482
emits: ['foo'],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { ref, render, h, nodeOps, nextTick } from '@vue/runtime-test'
2+
3+
describe('component: slots', () => {
4+
// TODO more tests for slots normalization etc.
5+
6+
test('should respect $stable flag', async () => {
7+
const flag1 = ref(1)
8+
const flag2 = ref(2)
9+
const spy = jest.fn()
10+
11+
const Child = () => {
12+
spy()
13+
return 'child'
14+
}
15+
16+
const App = {
17+
setup() {
18+
return () => [
19+
flag1.value,
20+
h(
21+
Child,
22+
{ n: flag2.value },
23+
{
24+
foo: () => 'foo',
25+
$stable: true
26+
}
27+
)
28+
]
29+
}
30+
}
31+
32+
render(h(App), nodeOps.createElement('div'))
33+
expect(spy).toHaveBeenCalledTimes(1)
34+
35+
// parent re-render, props didn't change, slots are stable
36+
// -> child should not update
37+
flag1.value++
38+
await nextTick()
39+
expect(spy).toHaveBeenCalledTimes(1)
40+
41+
// parent re-render, props changed
42+
// -> child should update
43+
flag2.value++
44+
await nextTick()
45+
expect(spy).toHaveBeenCalledTimes(2)
46+
})
47+
})

packages/runtime-core/__tests__/errorHandling.spec.ts

+14-11
Original file line numberDiff line numberDiff line change
@@ -416,28 +416,29 @@ describe('error handling', () => {
416416
}
417417
}
418418

419-
let res: any
420419
const Child = {
420+
props: ['onFoo'],
421421
setup(props: any, { emit }: any) {
422-
res = emit('foo')
422+
emit('foo')
423423
return () => null
424424
}
425425
}
426426

427427
render(h(Comp), nodeOps.createElement('div'))
428-
429-
try {
430-
await Promise.all(res)
431-
} catch (e) {
432-
expect(e).toBe(err)
433-
}
428+
await nextTick()
434429
expect(fn).toHaveBeenCalledWith(err, 'component event handler')
435430
})
436431

437432
test('in component event handler via emit (async + array)', async () => {
438433
const err = new Error('foo')
439434
const fn = jest.fn()
440435

436+
const res: Promise<any>[] = []
437+
const createAsyncHandler = (p: Promise<any>) => () => {
438+
res.push(p)
439+
return p
440+
}
441+
441442
const Comp = {
442443
setup() {
443444
onErrorCaptured((err, instance, info) => {
@@ -446,15 +447,17 @@ describe('error handling', () => {
446447
})
447448
return () =>
448449
h(Child, {
449-
onFoo: [() => Promise.reject(err), () => Promise.resolve(1)]
450+
onFoo: [
451+
createAsyncHandler(Promise.reject(err)),
452+
createAsyncHandler(Promise.resolve(1))
453+
]
450454
})
451455
}
452456
}
453457

454-
let res: any
455458
const Child = {
456459
setup(props: any, { emit }: any) {
457-
res = emit('foo')
460+
emit('foo')
458461
return () => null
459462
}
460463
}

packages/runtime-core/src/componentEmits.ts

+5-8
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,20 @@ export type EmitFn<
2828
Options = ObjectEmitsOptions,
2929
Event extends keyof Options = keyof Options
3030
> = Options extends any[]
31-
? (event: Options[0], ...args: any[]) => unknown[]
31+
? (event: Options[0], ...args: any[]) => void
3232
: UnionToIntersection<
3333
{
3434
[key in Event]: Options[key] extends ((...args: infer Args) => any)
35-
? (event: key, ...args: Args) => unknown[]
36-
: (event: key, ...args: any[]) => unknown[]
35+
? (event: key, ...args: Args) => void
36+
: (event: key, ...args: any[]) => void
3737
}[Event]
3838
>
3939

4040
export function emit(
4141
instance: ComponentInternalInstance,
4242
event: string,
4343
...args: any[]
44-
): any[] {
44+
) {
4545
const props = instance.vnode.props || EMPTY_OBJ
4646

4747
if (__DEV__) {
@@ -74,15 +74,12 @@ export function emit(
7474
handler = props[`on${event}`] || props[`on${capitalize(event)}`]
7575
}
7676
if (handler) {
77-
const res = callWithAsyncErrorHandling(
77+
callWithAsyncErrorHandling(
7878
handler,
7979
instance,
8080
ErrorCodes.COMPONENT_EVENT_HANDLER,
8181
args
8282
)
83-
return isArray(res) ? res : [res]
84-
} else {
85-
return []
8683
}
8784
}
8885

0 commit comments

Comments
 (0)