Skip to content

Commit

Permalink
refactor(runtime-core): remove emit return value
Browse files Browse the repository at this point in the history
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
  • Loading branch information
yyx990803 committed Apr 10, 2020
1 parent a6e2b10 commit 55566e8
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 166 deletions.
146 changes: 0 additions & 146 deletions packages/runtime-core/__tests__/component.spec.ts

This file was deleted.

30 changes: 29 additions & 1 deletion packages/runtime-core/__tests__/componentEmits.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { mockWarn } from '@vue/shared'
import { render, defineComponent, h, nodeOps } from '@vue/runtime-test'
import { isEmitListener } from '../src/componentEmits'

describe('emits option', () => {
describe('component: emit', () => {
mockWarn()

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

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

test('should trigger array of listeners', async () => {
const Child = defineComponent({
setup(_, { emit }) {
emit('foo', 1)
return () => h('div')
}
})

const fn1 = jest.fn()
const fn2 = jest.fn()

const App = {
setup() {
return () =>
h(Child, {
onFoo: [fn1, fn2]
})
}
}

render(h(App), nodeOps.createElement('div'))
expect(fn1).toHaveBeenCalledTimes(1)
expect(fn1).toHaveBeenCalledWith(1)
expect(fn2).toHaveBeenCalledTimes(1)
expect(fn1).toHaveBeenCalledWith(1)
})

test('warning for undeclared event (array)', () => {
const Foo = defineComponent({
emits: ['foo'],
Expand Down
47 changes: 47 additions & 0 deletions packages/runtime-core/__tests__/componentSlots.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ref, render, h, nodeOps, nextTick } from '@vue/runtime-test'

describe('component: slots', () => {
// TODO more tests for slots normalization etc.

test('should respect $stable flag', async () => {
const flag1 = ref(1)
const flag2 = ref(2)
const spy = jest.fn()

const Child = () => {
spy()
return 'child'
}

const App = {
setup() {
return () => [
flag1.value,
h(
Child,
{ n: flag2.value },
{
foo: () => 'foo',
$stable: true
}
)
]
}
}

render(h(App), nodeOps.createElement('div'))
expect(spy).toHaveBeenCalledTimes(1)

// parent re-render, props didn't change, slots are stable
// -> child should not update
flag1.value++
await nextTick()
expect(spy).toHaveBeenCalledTimes(1)

// parent re-render, props changed
// -> child should update
flag2.value++
await nextTick()
expect(spy).toHaveBeenCalledTimes(2)
})
})
25 changes: 14 additions & 11 deletions packages/runtime-core/__tests__/errorHandling.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,28 +416,29 @@ describe('error handling', () => {
}
}

let res: any
const Child = {
props: ['onFoo'],
setup(props: any, { emit }: any) {
res = emit('foo')
emit('foo')
return () => null
}
}

render(h(Comp), nodeOps.createElement('div'))

try {
await Promise.all(res)
} catch (e) {
expect(e).toBe(err)
}
await nextTick()
expect(fn).toHaveBeenCalledWith(err, 'component event handler')
})

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

const res: Promise<any>[] = []
const createAsyncHandler = (p: Promise<any>) => () => {
res.push(p)
return p
}

const Comp = {
setup() {
onErrorCaptured((err, instance, info) => {
Expand All @@ -446,15 +447,17 @@ describe('error handling', () => {
})
return () =>
h(Child, {
onFoo: [() => Promise.reject(err), () => Promise.resolve(1)]
onFoo: [
createAsyncHandler(Promise.reject(err)),
createAsyncHandler(Promise.resolve(1))
]
})
}
}

let res: any
const Child = {
setup(props: any, { emit }: any) {
res = emit('foo')
emit('foo')
return () => null
}
}
Expand Down
13 changes: 5 additions & 8 deletions packages/runtime-core/src/componentEmits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@ export type EmitFn<
Options = ObjectEmitsOptions,
Event extends keyof Options = keyof Options
> = Options extends any[]
? (event: Options[0], ...args: any[]) => unknown[]
? (event: Options[0], ...args: any[]) => void
: UnionToIntersection<
{
[key in Event]: Options[key] extends ((...args: infer Args) => any)
? (event: key, ...args: Args) => unknown[]
: (event: key, ...args: any[]) => unknown[]
? (event: key, ...args: Args) => void
: (event: key, ...args: any[]) => void
}[Event]
>

export function emit(
instance: ComponentInternalInstance,
event: string,
...args: any[]
): any[] {
) {
const props = instance.vnode.props || EMPTY_OBJ

if (__DEV__) {
Expand Down Expand Up @@ -74,15 +74,12 @@ export function emit(
handler = props[`on${event}`] || props[`on${capitalize(event)}`]
}
if (handler) {
const res = callWithAsyncErrorHandling(
callWithAsyncErrorHandling(
handler,
instance,
ErrorCodes.COMPONENT_EVENT_HANDLER,
args
)
return isArray(res) ? res : [res]
} else {
return []
}
}

Expand Down

0 comments on commit 55566e8

Please sign in to comment.