fix(runtime-core/emits): merge emits options from mixins/extends
fix #1562
This commit is contained in:
parent
c2d3da9dc4
commit
ba3b3cdda9
@ -143,12 +143,47 @@ describe('component: emit', () => {
|
||||
expect(`event validation failed for event "foo"`).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('merging from mixins', () => {
|
||||
const mixin = {
|
||||
emits: {
|
||||
foo: (arg: number) => arg > 0
|
||||
}
|
||||
}
|
||||
const Foo = defineComponent({
|
||||
mixins: [mixin],
|
||||
render() {},
|
||||
created() {
|
||||
this.$emit('foo', -1)
|
||||
}
|
||||
})
|
||||
render(h(Foo), nodeOps.createElement('div'))
|
||||
expect(`event validation failed for event "foo"`).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('isEmitListener', () => {
|
||||
expect(isEmitListener(['click'], 'onClick')).toBe(true)
|
||||
expect(isEmitListener(['click'], 'onclick')).toBe(false)
|
||||
expect(isEmitListener({ click: null }, 'onClick')).toBe(true)
|
||||
expect(isEmitListener({ click: null }, 'onclick')).toBe(false)
|
||||
expect(isEmitListener(['click'], 'onBlick')).toBe(false)
|
||||
expect(isEmitListener({ click: null }, 'onBlick')).toBe(false)
|
||||
const def1 = { emits: ['click'] }
|
||||
expect(isEmitListener(def1, 'onClick')).toBe(true)
|
||||
expect(isEmitListener(def1, 'onclick')).toBe(false)
|
||||
expect(isEmitListener(def1, 'onBlick')).toBe(false)
|
||||
|
||||
const def2 = { emits: { click: null } }
|
||||
expect(isEmitListener(def2, 'onClick')).toBe(true)
|
||||
expect(isEmitListener(def2, 'onclick')).toBe(false)
|
||||
expect(isEmitListener(def2, 'onBlick')).toBe(false)
|
||||
|
||||
const mixin1 = { emits: ['foo'] }
|
||||
const mixin2 = { emits: ['bar'] }
|
||||
const extend = { emits: ['baz'] }
|
||||
const def3 = {
|
||||
emits: { click: null },
|
||||
mixins: [mixin1, mixin2],
|
||||
extends: extend
|
||||
}
|
||||
expect(isEmitListener(def3, 'onClick')).toBe(true)
|
||||
expect(isEmitListener(def3, 'onFoo')).toBe(true)
|
||||
expect(isEmitListener(def3, 'onBar')).toBe(true)
|
||||
expect(isEmitListener(def3, 'onBaz')).toBe(true)
|
||||
expect(isEmitListener(def3, 'onclick')).toBe(false)
|
||||
expect(isEmitListener(def3, 'onBlick')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
@ -59,6 +59,10 @@ export interface ComponentInternalOptions {
|
||||
* @internal
|
||||
*/
|
||||
__props?: NormalizedPropsOptions | []
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
__emits?: ObjectEmitsOptions
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
@ -6,9 +6,9 @@ import {
|
||||
capitalize,
|
||||
hyphenate,
|
||||
isFunction,
|
||||
def
|
||||
extend
|
||||
} from '@vue/shared'
|
||||
import { ComponentInternalInstance } from './component'
|
||||
import { ComponentInternalInstance, Component } from './component'
|
||||
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
|
||||
import { warn } from './warning'
|
||||
import { normalizePropsOptions } from './componentProps'
|
||||
@ -43,7 +43,7 @@ export function emit(
|
||||
const props = instance.vnode.props || EMPTY_OBJ
|
||||
|
||||
if (__DEV__) {
|
||||
const options = normalizeEmitsOptions(instance.type.emits)
|
||||
const options = normalizeEmitsOptions(instance.type)
|
||||
if (options) {
|
||||
if (!(event in options)) {
|
||||
const propsOptions = normalizePropsOptions(instance.type)[0]
|
||||
@ -84,34 +84,52 @@ export function emit(
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeEmitsOptions(
|
||||
options: EmitsOptions | undefined
|
||||
function normalizeEmitsOptions(
|
||||
comp: Component
|
||||
): ObjectEmitsOptions | undefined {
|
||||
if (!options) {
|
||||
return
|
||||
} else if (isArray(options)) {
|
||||
if ((options as any)._n) {
|
||||
return (options as any)._n
|
||||
if (hasOwn(comp, '__emits')) {
|
||||
return comp.__emits
|
||||
}
|
||||
const normalized: ObjectEmitsOptions = {}
|
||||
options.forEach(key => (normalized[key] = null))
|
||||
def(options, '_n', normalized)
|
||||
return normalized
|
||||
|
||||
const raw = comp.emits
|
||||
let normalized: ObjectEmitsOptions = {}
|
||||
|
||||
// apply mixin/extends props
|
||||
let hasExtends = false
|
||||
if (__FEATURE_OPTIONS__ && !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 {
|
||||
return options
|
||||
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(emits: EmitsOptions, key: string): boolean {
|
||||
export function isEmitListener(comp: Component, key: string): boolean {
|
||||
if (!isOn(key)) {
|
||||
return false
|
||||
}
|
||||
const emits = normalizeEmitsOptions(comp)
|
||||
return (
|
||||
isOn(key) &&
|
||||
(hasOwn(
|
||||
(emits = normalizeEmitsOptions(emits) as ObjectEmitsOptions),
|
||||
key[2].toLowerCase() + key.slice(3)
|
||||
) ||
|
||||
!!emits &&
|
||||
(hasOwn(emits, key[2].toLowerCase() + key.slice(3)) ||
|
||||
hasOwn(emits, key.slice(2)))
|
||||
)
|
||||
}
|
||||
|
@ -242,8 +242,6 @@ function setFullProps(
|
||||
attrs: Data
|
||||
) {
|
||||
const [options, needCastKeys] = normalizePropsOptions(instance.type)
|
||||
const emits = instance.type.emits
|
||||
|
||||
if (rawProps) {
|
||||
for (const key in rawProps) {
|
||||
const value = rawProps[key]
|
||||
@ -256,7 +254,7 @@ function setFullProps(
|
||||
let camelKey
|
||||
if (options && hasOwn(options, (camelKey = camelize(key)))) {
|
||||
props[camelKey] = value
|
||||
} else if (!emits || !isEmitListener(emits, key)) {
|
||||
} else if (!isEmitListener(instance.type, key)) {
|
||||
// Any non-declared (either as a prop or an emitted event) props are put
|
||||
// into a separate `attrs` object for spreading. Make sure to preserve
|
||||
// original key casing
|
||||
|
Loading…
Reference in New Issue
Block a user