Skip to content

Commit

Permalink
feat(runtime-core): type and attr fallthrough support for emits option
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Apr 3, 2020
1 parent c409d4f commit bf473a6
Show file tree
Hide file tree
Showing 9 changed files with 351 additions and 97 deletions.
69 changes: 68 additions & 1 deletion packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
onUpdated,
defineComponent,
openBlock,
createBlock
createBlock,
FunctionalComponent
} from '@vue/runtime-dom'
import { mockWarn } from '@vue/shared'

Expand Down Expand Up @@ -428,4 +429,70 @@ describe('attribute fallthrough', () => {
await nextTick()
expect(root.innerHTML).toBe(`<div aria-hidden="false" class="barr"></div>`)
})

it('should not let listener fallthrough when declared in emits (stateful)', () => {
const Child = defineComponent({
emits: ['click'],
render() {
return h(
'button',
{
onClick: () => {
this.$emit('click', 'custom')
}
},
'hello'
)
}
})

const onClick = jest.fn()
const App = {
render() {
return h(Child, {
onClick
})
}
}

const root = document.createElement('div')
document.body.appendChild(root)
render(h(App), root)

const node = root.children[0] as HTMLElement
node.dispatchEvent(new CustomEvent('click'))
expect(onClick).toHaveBeenCalledTimes(1)
expect(onClick).toHaveBeenCalledWith('custom')
})

it('should not let listener fallthrough when declared in emits (functional)', () => {
const Child: FunctionalComponent<{}, { click: any }> = (_, { emit }) => {
// should not be in props
expect((_ as any).onClick).toBeUndefined()
return h('button', {
onClick: () => {
emit('click', 'custom')
}
})
}
Child.emits = ['click']

const onClick = jest.fn()
const App = {
render() {
return h(Child, {
onClick
})
}
}

const root = document.createElement('div')
document.body.appendChild(root)
render(h(App), root)

const node = root.children[0] as HTMLElement
node.dispatchEvent(new CustomEvent('click'))
expect(onClick).toHaveBeenCalledTimes(1)
expect(onClick).toHaveBeenCalledWith('custom')
})
})
47 changes: 36 additions & 11 deletions packages/runtime-core/src/apiDefineComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
MethodOptions,
ComponentOptionsWithoutProps,
ComponentOptionsWithArrayProps,
ComponentOptionsWithObjectProps
ComponentOptionsWithObjectProps,
EmitsOptions
} from './apiOptions'
import { SetupContext, RenderFunction } from './component'
import { ComponentPublicInstance } from './componentProxy'
Expand Down Expand Up @@ -39,20 +40,23 @@ export function defineComponent<Props, RawBindings = object>(
// (uses user defined props interface)
// return type is for Vetur and TSX support
export function defineComponent<
Props,
RawBindings,
D,
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {}
M extends MethodOptions = {},
E extends EmitsOptions = Record<string, any>,
EE extends string = string
>(
options: ComponentOptionsWithoutProps<Props, RawBindings, D, C, M>
options: ComponentOptionsWithoutProps<Props, RawBindings, D, C, M, E, EE>
): {
new (): ComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
E,
VNodeProps & Props
>
}
Expand All @@ -65,12 +69,22 @@ export function defineComponent<
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {}
M extends MethodOptions = {},
E extends EmitsOptions = Record<string, any>,
EE extends string = string
>(
options: ComponentOptionsWithArrayProps<PropNames, RawBindings, D, C, M>
options: ComponentOptionsWithArrayProps<
PropNames,
RawBindings,
D,
C,
M,
E,
EE
>
): {
// array props technically doesn't place any contraints on props in TSX
new (): ComponentPublicInstance<VNodeProps, RawBindings, D, C, M>
new (): ComponentPublicInstance<VNodeProps, RawBindings, D, C, M, E>
}

// overload 4: object format with object props declaration
Expand All @@ -82,16 +96,27 @@ export function defineComponent<
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {}
M extends MethodOptions = {},
E extends EmitsOptions = Record<string, any>,
EE extends string = string
>(
options: ComponentOptionsWithObjectProps<PropsOptions, RawBindings, D, C, M>
options: ComponentOptionsWithObjectProps<
PropsOptions,
RawBindings,
D,
C,
M,
E,
EE
>
): {
new (): ComponentPublicInstance<
ExtractPropTypes<PropsOptions>,
RawBindings,
D,
C,
M,
E,
VNodeProps & ExtractPropTypes<PropsOptions, false>
>
}
Expand Down
34 changes: 23 additions & 11 deletions packages/runtime-core/src/apiOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ export interface ComponentOptionsBase<
RawBindings,
D,
C extends ComputedOptions,
M extends MethodOptions
> extends LegacyOptions<Props, RawBindings, D, C, M>, SFCInternalOptions {
M extends MethodOptions,
E extends EmitsOptions,
EE extends string = string
> extends LegacyOptions<Props, D, C, M>, SFCInternalOptions {
setup?: (
this: void,
props: Props,
ctx: SetupContext
ctx: SetupContext<E>
) => RawBindings | RenderFunction | void
name?: string
template?: string | object // can be a direct DOM node
Expand All @@ -75,6 +77,7 @@ export interface ComponentOptionsBase<
components?: Record<string, PublicAPIComponent>
directives?: Record<string, Directive>
inheritAttrs?: boolean
emits?: E | EE[]

// Internal ------------------------------------------------------------------

Expand All @@ -97,32 +100,40 @@ export type ComponentOptionsWithoutProps<
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {}
> = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
M extends MethodOptions = {},
E extends EmitsOptions = Record<string, any>,
EE extends string = string
> = ComponentOptionsBase<Props, RawBindings, D, C, M, E, EE> & {
props?: undefined
} & ThisType<ComponentPublicInstance<{}, RawBindings, D, C, M, Readonly<Props>>>
} & ThisType<
ComponentPublicInstance<{}, RawBindings, D, C, M, E, Readonly<Props>>
>

export type ComponentOptionsWithArrayProps<
PropNames extends string = string,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
E extends EmitsOptions = Record<string, any>,
EE extends string = string,
Props = Readonly<{ [key in PropNames]?: any }>
> = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
> = ComponentOptionsBase<Props, RawBindings, D, C, M, E, EE> & {
props: PropNames[]
} & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M>>
} & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M, E>>

export type ComponentOptionsWithObjectProps<
PropsOptions = ComponentObjectPropsOptions,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
E extends EmitsOptions = Record<string, any>,
EE extends string = string,
Props = Readonly<ExtractPropTypes<PropsOptions>>
> = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
> = ComponentOptionsBase<Props, RawBindings, D, C, M, E, EE> & {
props: PropsOptions
} & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M>>
} & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M, E>>

export type ComponentOptions =
| ComponentOptionsWithoutProps<any, any, any, any, any>
Expand All @@ -138,6 +149,8 @@ export interface MethodOptions {
[key: string]: Function
}

export type EmitsOptions = Record<string, any> | string[]

export type ExtractComputedReturns<T extends any> = {
[key in keyof T]: T[key] extends { get: Function }
? ReturnType<T[key]['get']>
Expand All @@ -162,7 +175,6 @@ type ComponentInjectOptions =

export interface LegacyOptions<
Props,
RawBindings,
D,
C extends ComputedOptions,
M extends MethodOptions
Expand Down
40 changes: 30 additions & 10 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from './errorHandling'
import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
import { Directive, validateDirectiveName } from './directives'
import { applyOptions, ComponentOptions } from './apiOptions'
import { applyOptions, ComponentOptions, EmitsOptions } from './apiOptions'
import {
EMPTY_OBJ,
isFunction,
Expand Down Expand Up @@ -52,9 +52,13 @@ export interface SFCInternalOptions {
__hmrUpdated?: boolean
}

export interface FunctionalComponent<P = {}> extends SFCInternalOptions {
(props: P, ctx: SetupContext): VNodeChild
export interface FunctionalComponent<
P = {},
E extends EmitsOptions = Record<string, any>
> extends SFCInternalOptions {
(props: P, ctx: SetupContext<E>): any
props?: ComponentPropsOptions<P>
emits?: E | (keyof E)[]
inheritAttrs?: boolean
displayName?: string
}
Expand Down Expand Up @@ -92,12 +96,29 @@ export const enum LifecycleHooks {
ERROR_CAPTURED = 'ec'
}

export type Emit = (event: string, ...args: unknown[]) => any[]

export interface SetupContext {
type UnionToIntersection<U> = (U extends any
? (k: U) => void
: never) extends ((k: infer I) => void)
? I
: never

export type Emit<
Options = Record<string, any>,
Event extends keyof Options = keyof Options
> = Options extends any[]
? (event: Options[0], ...args: any[]) => unknown[]
: UnionToIntersection<
{
[key in Event]: Options[key] extends ((...args: infer Args) => any)
? (event: key, ...args: Args) => unknown[]
: (event: key, ...args: any[]) => unknown[]
}[Event]
>

export interface SetupContext<E = Record<string, any>> {
attrs: Data
slots: Slots
emit: Emit
emit: Emit<E>
}

export type RenderFunction = {
Expand Down Expand Up @@ -248,7 +269,7 @@ export function createComponentInstance(
rtc: null,
ec: null,

emit: (event, ...args): any[] => {
emit: (event: string, ...args: any[]): any[] => {
const props = instance.vnode.props || EMPTY_OBJ
let handler = props[`on${event}`] || props[`on${capitalize(event)}`]
if (!handler && event.indexOf('update:') === 0) {
Expand Down Expand Up @@ -303,9 +324,8 @@ export function setupComponent(
isSSR = false
) {
isInSSRComponentSetup = isSSR
const propsOptions = instance.type.props
const { props, children, shapeFlag } = instance.vnode
resolveProps(instance, props, propsOptions)
resolveProps(instance, props)
resolveSlots(instance, children)

// setup stateful logic
Expand Down
Loading

0 comments on commit bf473a6

Please sign in to comment.