vue3-yuanma/packages/runtime-core/src/componentEmits.ts
Evan You 54727f9874 feat: provide ability to overwrite feature flags in esm-bundler builds
e.g. by replacing `__VUE_OPTIONS_API__` to `false` using webpack's
`DefinePlugin`, the final bundle will drop all code supporting the
options API.

This does not break existing usage, but requires the user to explicitly
configure the feature flags via bundlers to properly tree-shake the
disabled branches. As a result, users will see a console warning if
the flags have not been properly configured.
2020-07-20 21:51:30 -04:00

145 lines
4.0 KiB
TypeScript

import {
isArray,
isOn,
hasOwn,
EMPTY_OBJ,
capitalize,
hyphenate,
isFunction,
extend
} from '@vue/shared'
import { ComponentInternalInstance, Component } from './component'
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
import { warn } from './warning'
import { normalizePropsOptions } from './componentProps'
import { UnionToIntersection } from './helpers/typeUtils'
export type ObjectEmitsOptions = Record<
string,
((...args: any[]) => any) | null
>
export type EmitsOptions = ObjectEmitsOptions | string[]
export type EmitFn<
Options = ObjectEmitsOptions,
Event extends keyof Options = keyof Options
> = Options extends any[]
? (event: Options[0], ...args: any[]) => void
: {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function
? (event: string, ...args: any[]) => void
: UnionToIntersection<
{
[key in Event]: Options[key] extends ((...args: infer Args) => any)
? (event: key, ...args: Args) => void
: (event: key, ...args: any[]) => void
}[Event]
>
export function emit(
instance: ComponentInternalInstance,
event: string,
...args: any[]
) {
const props = instance.vnode.props || EMPTY_OBJ
if (__DEV__) {
const options = normalizeEmitsOptions(instance.type)
if (options) {
if (!(event in options)) {
const propsOptions = normalizePropsOptions(instance.type)[0]
if (!propsOptions || !(`on` + capitalize(event) in propsOptions)) {
warn(
`Component emitted event "${event}" but it is neither declared in ` +
`the emits option nor as an "on${capitalize(event)}" prop.`
)
}
} 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 handlerName = `on${capitalize(event)}`
let handler = props[handlerName]
// for v-model update:xxx events, also trigger kebab-case equivalent
// for props passed via kebab-case
if (!handler && event.startsWith('update:')) {
handlerName = `on${capitalize(hyphenate(event))}`
handler = props[handlerName]
}
if (!handler) {
handler = props[handlerName + `Once`]
if (!instance.emitted) {
;(instance.emitted = {} as Record<string, boolean>)[handlerName] = true
} else if (instance.emitted[handlerName]) {
return
}
}
if (handler) {
callWithAsyncErrorHandling(
handler,
instance,
ErrorCodes.COMPONENT_EVENT_HANDLER,
args
)
}
}
function normalizeEmitsOptions(
comp: Component
): ObjectEmitsOptions | undefined {
if (hasOwn(comp, '__emits')) {
return comp.__emits
}
const raw = comp.emits
let normalized: ObjectEmitsOptions = {}
// apply mixin/extends props
let hasExtends = false
if (__FEATURE_OPTIONS_API__ && !isFunction(comp)) {
if (comp.extends) {
hasExtends = true
extend(normalized, normalizeEmitsOptions(comp.extends))
}
if (comp.mixins) {
hasExtends = true
comp.mixins.forEach(m => extend(normalized, normalizeEmitsOptions(m)))
}
}
if (!raw && !hasExtends) {
return (comp.__emits = undefined)
}
if (isArray(raw)) {
raw.forEach(key => (normalized[key] = null))
} else {
extend(normalized, raw)
}
return (comp.__emits = normalized)
}
// 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(comp: Component, key: string): boolean {
let emits: ObjectEmitsOptions | undefined
if (!isOn(key) || !(emits = normalizeEmitsOptions(comp))) {
return false
}
key = key.replace(/Once$/, '')
return (
hasOwn(emits, key[2].toLowerCase() + key.slice(3)) ||
hasOwn(emits, key.slice(2))
)
}