feat(runtime-core): emits validation and warnings

This commit is contained in:
Evan You
2020-04-03 20:40:34 -04:00
parent 24e9efcc21
commit c7c3a6a3be
4 changed files with 142 additions and 11 deletions

View File

@@ -4,10 +4,12 @@ import {
hasOwn,
EMPTY_OBJ,
capitalize,
hyphenate
hyphenate,
isFunction
} from '@vue/shared'
import { ComponentInternalInstance } from './component'
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
import { warn } from './warning'
export type ObjectEmitsOptions = Record<
string,
@@ -40,6 +42,29 @@ export function emit(
...args: any[]
): any[] {
const props = instance.vnode.props || EMPTY_OBJ
if (__DEV__) {
const options = normalizeEmitsOptions(instance.type.emits)
if (options) {
if (!(event in options)) {
warn(
`Component emitted event "${event}" but it is not declared in the ` +
`emits option.`
)
} else {
const validator = options[event]
if (isFunction(validator)) {
const isValid = validator(...args)
if (!isValid) {
warn(
`Invalid event arguments: event validation failed for event "${event}".`
)
}
}
}
}
}
let handler = props[`on${event}`] || props[`on${capitalize(event)}`]
// for v-model update:xxx events, also trigger kebab-case equivalent
// for props passed via kebab-case
@@ -81,13 +106,13 @@ export function normalizeEmitsOptions(
// Check if an incoming prop key is a declared emit event listener.
// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are
// both considered matched listeners.
export function isEmitListener(
emits: ObjectEmitsOptions,
key: string
): boolean {
export function isEmitListener(emits: EmitsOptions, key: string): boolean {
return (
isOn(key) &&
(hasOwn(emits, key[2].toLowerCase() + key.slice(3)) ||
(hasOwn(
(emits = normalizeEmitsOptions(emits) as ObjectEmitsOptions),
key[2].toLowerCase() + key.slice(3)
) ||
hasOwn(emits, key.slice(2)))
)
}

View File

@@ -102,7 +102,7 @@ export type ComponentOptionsWithoutProps<
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
E extends EmitsOptions = Record<string, any>,
E extends EmitsOptions = EmitsOptions,
EE extends string = string
> = ComponentOptionsBase<Props, RawBindings, D, C, M, E, EE> & {
props?: undefined
@@ -116,7 +116,7 @@ export type ComponentOptionsWithArrayProps<
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
E extends EmitsOptions = Record<string, any>,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
Props = Readonly<{ [key in PropNames]?: any }>
> = ComponentOptionsBase<Props, RawBindings, D, C, M, E, EE> & {
@@ -129,7 +129,7 @@ export type ComponentOptionsWithObjectProps<
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
E extends EmitsOptions = Record<string, any>,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
Props = Readonly<ExtractPropTypes<PropsOptions>>
> = ComponentOptionsBase<Props, RawBindings, D, C, M, E, EE> & {

View File

@@ -18,7 +18,7 @@ import {
} from '@vue/shared'
import { warn } from './warning'
import { Data, ComponentInternalInstance } from './component'
import { normalizeEmitsOptions, isEmitListener } from './componentEmits'
import { isEmitListener } from './componentEmits'
export type ComponentPropsOptions<P = Data> =
| ComponentObjectPropsOptions<P>
@@ -115,7 +115,7 @@ export function resolveProps(
}
const { 0: options, 1: needCastKeys } = normalizePropsOptions(_options)!
const emits = normalizeEmitsOptions(instance.type.emits)
const emits = instance.type.emits
const props: Data = {}
let attrs: Data | undefined = undefined