mirror of https://github.com/vuejs/core.git
feat(runtime-vapor): component emits (#103)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
parent
ea5f7ec076
commit
cde91e4fb5
|
@ -72,6 +72,7 @@ The code provided here is a duplicate from `runtime-core` as Vapor cannot import
|
|||
|
||||
- packages/runtime-vapor/src/apiWatch.ts
|
||||
- packages/runtime-vapor/src/component.ts
|
||||
- packages/runtime-vapor/src/componentEmits.ts
|
||||
- packages/runtime-vapor/src/componentProps.ts
|
||||
- packages/runtime-vapor/src/enums.ts
|
||||
- packages/runtime-vapor/src/errorHandling.ts
|
||||
|
|
|
@ -0,0 +1,488 @@
|
|||
// NOTE: this test cases are based on paclages/runtime-core/__tests__/componentEmits.spec.ts
|
||||
|
||||
// Note: emits and listener fallthrough is tested in
|
||||
// ./rendererAttrsFallthrough.spec.ts.
|
||||
|
||||
import {
|
||||
defineComponent,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
render,
|
||||
unmountComponent,
|
||||
} from '../src'
|
||||
import { isEmitListener } from '../src/componentEmits'
|
||||
|
||||
let host: HTMLElement
|
||||
|
||||
const initHost = () => {
|
||||
host = document.createElement('div')
|
||||
host.setAttribute('id', 'host')
|
||||
document.body.appendChild(host)
|
||||
}
|
||||
beforeEach(() => initHost())
|
||||
afterEach(() => host.remove())
|
||||
|
||||
describe('component: emit', () => {
|
||||
test('trigger handlers', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
setup(_: any, { emit }: any) {
|
||||
emit('foo')
|
||||
emit('bar')
|
||||
emit('!baz')
|
||||
},
|
||||
})
|
||||
const onfoo = vi.fn()
|
||||
const onBar = vi.fn()
|
||||
const onBaz = vi.fn()
|
||||
render(
|
||||
Foo,
|
||||
{
|
||||
get onfoo() {
|
||||
return onfoo
|
||||
},
|
||||
get onBar() {
|
||||
return onBar
|
||||
},
|
||||
get ['on!baz']() {
|
||||
return onBaz
|
||||
},
|
||||
},
|
||||
'#host',
|
||||
)
|
||||
|
||||
expect(onfoo).not.toHaveBeenCalled()
|
||||
expect(onBar).toHaveBeenCalled()
|
||||
expect(onBaz).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('trigger camelCase handler', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
setup(_: any, { emit }: any) {
|
||||
emit('test-event')
|
||||
},
|
||||
})
|
||||
|
||||
const fooSpy = vi.fn()
|
||||
render(
|
||||
Foo,
|
||||
{
|
||||
get onTestEvent() {
|
||||
return fooSpy
|
||||
},
|
||||
},
|
||||
'#host',
|
||||
)
|
||||
expect(fooSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('trigger kebab-case handler', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
setup(_: any, { emit }: any) {
|
||||
emit('test-event')
|
||||
},
|
||||
})
|
||||
|
||||
const fooSpy = vi.fn()
|
||||
render(
|
||||
Foo,
|
||||
{
|
||||
get ['onTest-event']() {
|
||||
return fooSpy
|
||||
},
|
||||
},
|
||||
'#host',
|
||||
)
|
||||
expect(fooSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
// #3527
|
||||
test.todo('trigger mixed case handlers', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
setup(_: any, { emit }: any) {
|
||||
emit('test-event')
|
||||
emit('testEvent')
|
||||
},
|
||||
})
|
||||
|
||||
const fooSpy = vi.fn()
|
||||
const barSpy = vi.fn()
|
||||
render(
|
||||
Foo,
|
||||
// TODO: impl `toHandlers`
|
||||
{
|
||||
get ['onTest-Event']() {
|
||||
return fooSpy
|
||||
},
|
||||
get onTestEvent() {
|
||||
return barSpy
|
||||
},
|
||||
},
|
||||
'#host',
|
||||
)
|
||||
expect(fooSpy).toHaveBeenCalledTimes(1)
|
||||
expect(barSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
// for v-model:foo-bar usage in DOM templates
|
||||
test('trigger hyphenated events for update:xxx events', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
setup(_: any, { emit }: any) {
|
||||
emit('update:fooProp')
|
||||
emit('update:barProp')
|
||||
},
|
||||
})
|
||||
|
||||
const fooSpy = vi.fn()
|
||||
const barSpy = vi.fn()
|
||||
render(
|
||||
Foo,
|
||||
{
|
||||
get ['onUpdate:fooProp']() {
|
||||
return fooSpy
|
||||
},
|
||||
get ['onUpdate:bar-prop']() {
|
||||
return barSpy
|
||||
},
|
||||
},
|
||||
'#host',
|
||||
)
|
||||
|
||||
expect(fooSpy).toHaveBeenCalled()
|
||||
expect(barSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('should trigger array of listeners', async () => {
|
||||
const App = defineComponent({
|
||||
render() {},
|
||||
setup(_: any, { emit }: any) {
|
||||
emit('foo', 1)
|
||||
},
|
||||
})
|
||||
|
||||
const fn1 = vi.fn()
|
||||
const fn2 = vi.fn()
|
||||
|
||||
render(
|
||||
App,
|
||||
{
|
||||
get onFoo() {
|
||||
return [fn1, fn2]
|
||||
},
|
||||
},
|
||||
'#host',
|
||||
)
|
||||
expect(fn1).toHaveBeenCalledTimes(1)
|
||||
expect(fn1).toHaveBeenCalledWith(1)
|
||||
expect(fn2).toHaveBeenCalledTimes(1)
|
||||
expect(fn2).toHaveBeenCalledWith(1)
|
||||
})
|
||||
|
||||
test.todo('warning for undeclared event (array)', () => {
|
||||
// TODO: warning
|
||||
})
|
||||
|
||||
test.todo('warning for undeclared event (object)', () => {
|
||||
// TODO: warning
|
||||
})
|
||||
|
||||
test('should not warn if has equivalent onXXX prop', () => {
|
||||
const Foo = defineComponent({
|
||||
props: ['onFoo'],
|
||||
emits: [],
|
||||
render() {},
|
||||
setup(_: any, { emit }: any) {
|
||||
emit('foo')
|
||||
},
|
||||
})
|
||||
render(Foo, {}, '#host')
|
||||
expect(
|
||||
`Component emitted event "foo" but it is neither declared`,
|
||||
).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test.todo('validator warning', () => {
|
||||
// TODO: warning validator
|
||||
})
|
||||
|
||||
// NOTE: not supported mixins
|
||||
// test.todo('merging from mixins', () => {})
|
||||
|
||||
// #2651
|
||||
// test.todo(
|
||||
// 'should not attach normalized object when mixins do not contain emits',
|
||||
// () => {},
|
||||
// )
|
||||
|
||||
test('.once', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
emits: {
|
||||
foo: null,
|
||||
bar: null,
|
||||
},
|
||||
setup(_: any, { emit }: any) {
|
||||
emit('foo')
|
||||
emit('foo')
|
||||
emit('bar')
|
||||
emit('bar')
|
||||
},
|
||||
})
|
||||
const fn = vi.fn()
|
||||
const barFn = vi.fn()
|
||||
render(
|
||||
Foo,
|
||||
{
|
||||
get onFooOnce() {
|
||||
return fn
|
||||
},
|
||||
get onBarOnce() {
|
||||
return barFn
|
||||
},
|
||||
},
|
||||
'#host',
|
||||
)
|
||||
expect(fn).toHaveBeenCalledTimes(1)
|
||||
expect(barFn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('.once with normal listener of the same name', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
emits: {
|
||||
foo: null,
|
||||
},
|
||||
setup(_: any, { emit }: any) {
|
||||
emit('foo')
|
||||
emit('foo')
|
||||
},
|
||||
})
|
||||
const onFoo = vi.fn()
|
||||
const onFooOnce = vi.fn()
|
||||
render(
|
||||
Foo,
|
||||
{
|
||||
get onFoo() {
|
||||
return onFoo
|
||||
},
|
||||
get onFooOnce() {
|
||||
return onFooOnce
|
||||
},
|
||||
},
|
||||
'#host',
|
||||
)
|
||||
expect(onFoo).toHaveBeenCalledTimes(2)
|
||||
expect(onFooOnce).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('.number modifier should work with v-model on component', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
setup(_: any, { emit }: any) {
|
||||
emit('update:modelValue', '1')
|
||||
emit('update:foo', '2')
|
||||
},
|
||||
})
|
||||
const fn1 = vi.fn()
|
||||
const fn2 = vi.fn()
|
||||
render(
|
||||
Foo,
|
||||
{
|
||||
get modelValue() {
|
||||
return null
|
||||
},
|
||||
get modelModifiers() {
|
||||
return { number: true }
|
||||
},
|
||||
get ['onUpdate:modelValue']() {
|
||||
return fn1
|
||||
},
|
||||
get foo() {
|
||||
return null
|
||||
},
|
||||
get fooModifiers() {
|
||||
return { number: true }
|
||||
},
|
||||
get ['onUpdate:foo']() {
|
||||
return fn2
|
||||
},
|
||||
},
|
||||
'#host',
|
||||
)
|
||||
expect(fn1).toHaveBeenCalledTimes(1)
|
||||
expect(fn1).toHaveBeenCalledWith(1)
|
||||
expect(fn2).toHaveBeenCalledTimes(1)
|
||||
expect(fn2).toHaveBeenCalledWith(2)
|
||||
})
|
||||
|
||||
test('.trim modifier should work with v-model on component', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
setup(_: any, { emit }: any) {
|
||||
emit('update:modelValue', ' one ')
|
||||
emit('update:foo', ' two ')
|
||||
},
|
||||
})
|
||||
const fn1 = vi.fn()
|
||||
const fn2 = vi.fn()
|
||||
render(
|
||||
Foo,
|
||||
{
|
||||
get modelValue() {
|
||||
return null
|
||||
},
|
||||
get modelModifiers() {
|
||||
return { trim: true }
|
||||
},
|
||||
get ['onUpdate:modelValue']() {
|
||||
return fn1
|
||||
},
|
||||
get foo() {
|
||||
return null
|
||||
},
|
||||
get fooModifiers() {
|
||||
return { trim: true }
|
||||
},
|
||||
get 'onUpdate:foo'() {
|
||||
return fn2
|
||||
},
|
||||
},
|
||||
'#host',
|
||||
)
|
||||
expect(fn1).toHaveBeenCalledTimes(1)
|
||||
expect(fn1).toHaveBeenCalledWith('one')
|
||||
expect(fn2).toHaveBeenCalledTimes(1)
|
||||
expect(fn2).toHaveBeenCalledWith('two')
|
||||
})
|
||||
|
||||
test('.trim and .number modifiers should work with v-model on component', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
setup(_: any, { emit }: any) {
|
||||
emit('update:modelValue', ' +01.2 ')
|
||||
emit('update:foo', ' 1 ')
|
||||
},
|
||||
})
|
||||
const fn1 = vi.fn()
|
||||
const fn2 = vi.fn()
|
||||
render(
|
||||
Foo,
|
||||
{
|
||||
get modelValue() {
|
||||
return null
|
||||
},
|
||||
get modelModifiers() {
|
||||
return { trim: true, number: true }
|
||||
},
|
||||
get ['onUpdate:modelValue']() {
|
||||
return fn1
|
||||
},
|
||||
get foo() {
|
||||
return null
|
||||
},
|
||||
get fooModifiers() {
|
||||
return { trim: true, number: true }
|
||||
},
|
||||
get ['onUpdate:foo']() {
|
||||
return fn2
|
||||
},
|
||||
},
|
||||
'#host',
|
||||
)
|
||||
expect(fn1).toHaveBeenCalledTimes(1)
|
||||
expect(fn1).toHaveBeenCalledWith(1.2)
|
||||
expect(fn2).toHaveBeenCalledTimes(1)
|
||||
expect(fn2).toHaveBeenCalledWith(1)
|
||||
})
|
||||
|
||||
test('only trim string parameter when work with v-model on component', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
setup(_: any, { emit }: any) {
|
||||
emit('update:modelValue', ' foo ', { bar: ' bar ' })
|
||||
},
|
||||
})
|
||||
const fn = vi.fn()
|
||||
render(
|
||||
Foo,
|
||||
{
|
||||
get modelValue() {
|
||||
return null
|
||||
},
|
||||
get modelModifiers() {
|
||||
return { trim: true }
|
||||
},
|
||||
get ['onUpdate:modelValue']() {
|
||||
return fn
|
||||
},
|
||||
},
|
||||
'#host',
|
||||
)
|
||||
expect(fn).toHaveBeenCalledTimes(1)
|
||||
expect(fn).toHaveBeenCalledWith('foo', { bar: ' bar ' })
|
||||
})
|
||||
|
||||
test('isEmitListener', () => {
|
||||
const options = {
|
||||
get click() {
|
||||
return null
|
||||
},
|
||||
get 'test-event'() {
|
||||
return null
|
||||
},
|
||||
get fooBar() {
|
||||
return null
|
||||
},
|
||||
get FooBaz() {
|
||||
return null
|
||||
},
|
||||
}
|
||||
expect(isEmitListener(options, 'onClick')).toBe(true)
|
||||
expect(isEmitListener(options, 'onclick')).toBe(false)
|
||||
expect(isEmitListener(options, 'onBlick')).toBe(false)
|
||||
// .once listeners
|
||||
expect(isEmitListener(options, 'onClickOnce')).toBe(true)
|
||||
expect(isEmitListener(options, 'onclickOnce')).toBe(false)
|
||||
// kebab-case option
|
||||
expect(isEmitListener(options, 'onTestEvent')).toBe(true)
|
||||
// camelCase option
|
||||
expect(isEmitListener(options, 'onFooBar')).toBe(true)
|
||||
// PascalCase option
|
||||
expect(isEmitListener(options, 'onFooBaz')).toBe(true)
|
||||
})
|
||||
|
||||
test('does not emit after unmount', async () => {
|
||||
const fn = vi.fn()
|
||||
const Foo = defineComponent({
|
||||
emits: ['closing'],
|
||||
setup(_: any, { emit }: any) {
|
||||
onBeforeUnmount(async () => {
|
||||
await nextTick()
|
||||
emit('closing', true)
|
||||
})
|
||||
},
|
||||
render() {},
|
||||
})
|
||||
const i = render(
|
||||
Foo,
|
||||
{
|
||||
get onClosing() {
|
||||
return fn
|
||||
},
|
||||
},
|
||||
'#host',
|
||||
)
|
||||
await nextTick()
|
||||
unmountComponent(i)
|
||||
await nextTick()
|
||||
expect(fn).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// NOTE: not supported mixins
|
||||
// test.todo('merge string array emits', async () => {})
|
||||
// test.todo('merge object emits', async () => {})
|
||||
})
|
|
@ -8,6 +8,13 @@ import {
|
|||
type NormalizedPropsOptions,
|
||||
normalizePropsOptions,
|
||||
} from './componentProps'
|
||||
import {
|
||||
type EmitFn,
|
||||
type EmitsOptions,
|
||||
type ObjectEmitsOptions,
|
||||
emit,
|
||||
normalizeEmitsOptions,
|
||||
} from './componentEmits'
|
||||
|
||||
import type { Data } from '@vue/shared'
|
||||
import { VaporLifecycleHooks } from './enums'
|
||||
|
@ -17,10 +24,12 @@ export type Component = FunctionalComponent | ObjectComponent
|
|||
export type SetupFn = (props: any, ctx: any) => Block | Data
|
||||
export type FunctionalComponent = SetupFn & {
|
||||
props: ComponentPropsOptions
|
||||
emits: EmitsOptions
|
||||
render(ctx: any): Block
|
||||
}
|
||||
export interface ObjectComponent {
|
||||
props: ComponentPropsOptions
|
||||
emits: EmitsOptions
|
||||
setup?: SetupFn
|
||||
render(ctx: any): Block
|
||||
}
|
||||
|
@ -37,13 +46,21 @@ export interface ComponentInternalInstance {
|
|||
block: Block | null
|
||||
scope: EffectScope
|
||||
component: FunctionalComponent | ObjectComponent
|
||||
|
||||
// TODO: ExtraProps: key, ref, ...
|
||||
rawProps: { [key: string]: any }
|
||||
|
||||
// normalized options
|
||||
propsOptions: NormalizedPropsOptions
|
||||
emitsOptions: ObjectEmitsOptions | null
|
||||
|
||||
parent: ComponentInternalInstance | null
|
||||
|
||||
// state
|
||||
props: Data
|
||||
setupState: Data
|
||||
emit: EmitFn
|
||||
emitted: Record<string, boolean> | null
|
||||
refs: Data
|
||||
metadata: WeakMap<Node, ElementMetadata>
|
||||
|
||||
|
@ -139,6 +156,7 @@ export const unsetCurrentInstance = () => {
|
|||
let uid = 0
|
||||
export const createComponentInstance = (
|
||||
component: ObjectComponent | FunctionalComponent,
|
||||
rawProps: Data,
|
||||
): ComponentInternalInstance => {
|
||||
const instance: ComponentInternalInstance = {
|
||||
uid: uid++,
|
||||
|
@ -146,13 +164,18 @@ export const createComponentInstance = (
|
|||
container: null!, // set on mountComponent
|
||||
scope: new EffectScope(true /* detached */)!,
|
||||
component,
|
||||
rawProps,
|
||||
|
||||
// TODO: registory of parent
|
||||
parent: null,
|
||||
|
||||
// resolved props and emits options
|
||||
propsOptions: normalizePropsOptions(component),
|
||||
// emitsOptions: normalizeEmitsOptions(type, appContext), // TODO:
|
||||
emitsOptions: normalizeEmitsOptions(component),
|
||||
|
||||
// emit
|
||||
emit: null!, // to be set immediately
|
||||
emitted: null,
|
||||
|
||||
// state
|
||||
props: EMPTY_OBJ,
|
||||
|
@ -225,5 +248,8 @@ export const createComponentInstance = (
|
|||
*/
|
||||
// [VaporLifecycleHooks.SERVER_PREFETCH]: null,
|
||||
}
|
||||
|
||||
instance.emit = emit.bind(null, instance)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
// NOTE: runtime-core/src/componentEmits.ts
|
||||
|
||||
import {
|
||||
EMPTY_OBJ,
|
||||
type UnionToIntersection,
|
||||
camelize,
|
||||
extend,
|
||||
hasOwn,
|
||||
hyphenate,
|
||||
isArray,
|
||||
isOn,
|
||||
isString,
|
||||
looseToNumber,
|
||||
toHandlerKey,
|
||||
} from '@vue/shared'
|
||||
import type { Component, ComponentInternalInstance } from './component'
|
||||
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
||||
|
||||
export type ObjectEmitsOptions = Record<
|
||||
string,
|
||||
((...args: any[]) => any) | null // TODO: call validation?
|
||||
>
|
||||
|
||||
export type EmitsOptions = ObjectEmitsOptions | string[]
|
||||
|
||||
export type EmitFn<
|
||||
Options = ObjectEmitsOptions,
|
||||
Event extends keyof Options = keyof Options,
|
||||
> =
|
||||
Options extends Array<infer V>
|
||||
? (event: V, ...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,
|
||||
...rawArgs: any[]
|
||||
) {
|
||||
if (instance.isUnmounted) return
|
||||
const { rawProps } = instance
|
||||
|
||||
let args = rawArgs
|
||||
const isModelListener = event.startsWith('update:')
|
||||
|
||||
// for v-model update:xxx events, apply modifiers on args
|
||||
const modelArg = isModelListener && event.slice(7)
|
||||
|
||||
if (modelArg && modelArg in rawProps) {
|
||||
const modifiersKey = `${
|
||||
modelArg === 'modelValue' ? 'model' : modelArg
|
||||
}Modifiers`
|
||||
const { number, trim } = rawProps[modifiersKey] || EMPTY_OBJ
|
||||
if (trim) {
|
||||
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
|
||||
}
|
||||
if (number) {
|
||||
args = rawArgs.map(looseToNumber)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: warn
|
||||
|
||||
let handlerName
|
||||
let handler =
|
||||
rawProps[(handlerName = toHandlerKey(event))] ||
|
||||
// also try camelCase event handler (#2249)
|
||||
rawProps[(handlerName = toHandlerKey(camelize(event)))]
|
||||
// for v-model update:xxx events, also trigger kebab-case equivalent
|
||||
// for props passed via kebab-case
|
||||
if (!handler && isModelListener) {
|
||||
handler = rawProps[(handlerName = toHandlerKey(hyphenate(event)))]
|
||||
}
|
||||
|
||||
if (handler) {
|
||||
callWithAsyncErrorHandling(
|
||||
handler,
|
||||
instance,
|
||||
VaporErrorCodes.COMPONENT_EVENT_HANDLER,
|
||||
args,
|
||||
)
|
||||
}
|
||||
|
||||
const onceHandler = rawProps[`${handlerName}Once`]
|
||||
if (onceHandler) {
|
||||
if (!instance.emitted) {
|
||||
instance.emitted = {}
|
||||
} else if (instance.emitted[handlerName]) {
|
||||
return
|
||||
}
|
||||
instance.emitted[handlerName] = true
|
||||
callWithAsyncErrorHandling(
|
||||
onceHandler,
|
||||
instance,
|
||||
VaporErrorCodes.COMPONENT_EVENT_HANDLER,
|
||||
args,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeEmitsOptions(
|
||||
comp: Component,
|
||||
): ObjectEmitsOptions | null {
|
||||
// TODO: caching?
|
||||
|
||||
const raw = comp.emits
|
||||
let normalized: ObjectEmitsOptions = {}
|
||||
|
||||
if (isArray(raw)) {
|
||||
raw.forEach(key => (normalized[key] = null))
|
||||
} else {
|
||||
extend(normalized, raw)
|
||||
}
|
||||
|
||||
return 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(
|
||||
options: ObjectEmitsOptions | null,
|
||||
key: string,
|
||||
): boolean {
|
||||
if (!options || !isOn(key)) {
|
||||
return false
|
||||
}
|
||||
|
||||
key = key.slice(2).replace(/Once$/, '')
|
||||
return (
|
||||
hasOwn(options, key[0].toLowerCase() + key.slice(1)) ||
|
||||
hasOwn(options, hyphenate(key)) ||
|
||||
hasOwn(options, key)
|
||||
)
|
||||
}
|
|
@ -27,7 +27,7 @@ export function render(
|
|||
props: Data,
|
||||
container: string | ParentNode,
|
||||
): ComponentInternalInstance {
|
||||
const instance = createComponentInstance(comp)
|
||||
const instance = createComponentInstance(comp, props)
|
||||
initProps(instance, props)
|
||||
return mountComponent(instance, (container = normalizeContainer(container)))
|
||||
}
|
||||
|
@ -46,8 +46,8 @@ export function mountComponent(
|
|||
|
||||
const reset = setCurrentInstance(instance)
|
||||
const block = instance.scope.run(() => {
|
||||
const { component, props } = instance
|
||||
const ctx = { expose: () => {} }
|
||||
const { component, props, emit } = instance
|
||||
const ctx = { expose: () => {}, emit }
|
||||
|
||||
const setupFn =
|
||||
typeof component === 'function' ? component : component.setup
|
||||
|
|
Loading…
Reference in New Issue