feat(types): map declared emits to onXXX props in inferred prop types (#3926)
This commit is contained in:
		
							parent
							
								
									35cc7b0d31
								
							
						
					
					
						commit
						69344ff1ae
					
				| @ -1,3 +1,5 @@ | ||||
| semi: false | ||||
| singleQuote: true | ||||
| printWidth: 80 | ||||
| trailingComma: 'none' | ||||
| arrowParens: 'avoid' | ||||
|  | ||||
| @ -68,7 +68,7 @@ | ||||
|     "lint-staged": "^10.2.10", | ||||
|     "minimist": "^1.2.0", | ||||
|     "npm-run-all": "^4.1.5", | ||||
|     "prettier": "~1.14.0", | ||||
|     "prettier": "^2.3.1", | ||||
|     "puppeteer": "^10.0.0", | ||||
|     "rollup": "~2.38.5", | ||||
|     "rollup-plugin-node-builtins": "^2.1.2", | ||||
|  | ||||
| @ -18,7 +18,7 @@ import { | ||||
|   ComponentPropsOptions, | ||||
|   ExtractDefaultPropTypes | ||||
| } from './componentProps' | ||||
| import { EmitsOptions } from './componentEmits' | ||||
| import { EmitsOptions, EmitsToProps } from './componentEmits' | ||||
| import { isFunction } from '@vue/shared' | ||||
| import { VNodeProps } from './vnode' | ||||
| import { | ||||
| @ -41,7 +41,7 @@ export type DefineComponent< | ||||
|   E extends EmitsOptions = Record<string, any>, | ||||
|   EE extends string = string, | ||||
|   PP = PublicProps, | ||||
|   Props = Readonly<ExtractPropTypes<PropsOrPropOptions>>, | ||||
|   Props = Readonly<ExtractPropTypes<PropsOrPropOptions>> & EmitsToProps<E>, | ||||
|   Defaults = ExtractDefaultPropTypes<PropsOrPropOptions> | ||||
| > = ComponentPublicInstanceConstructor< | ||||
|   CreateComponentPublicInstance< | ||||
| @ -102,7 +102,7 @@ export function defineComponent< | ||||
|   EE extends string = string | ||||
| >( | ||||
|   options: ComponentOptionsWithoutProps< | ||||
|     Props, | ||||
|     Props & EmitsToProps<E>, | ||||
|     RawBindings, | ||||
|     D, | ||||
|     C, | ||||
|  | ||||
| @ -31,22 +31,38 @@ export type ObjectEmitsOptions = Record< | ||||
|   string, | ||||
|   ((...args: any[]) => any) | null | ||||
| > | ||||
| 
 | ||||
| export type EmitsOptions = ObjectEmitsOptions | string[] | ||||
| 
 | ||||
| export type EmitsToProps<T extends EmitsOptions> = T extends string[] | ||||
|   ? { | ||||
|       [K in string & `on${Capitalize<T[number]>}`]?: (...args: any[]) => any | ||||
|     } | ||||
|   : T extends ObjectEmitsOptions | ||||
|   ? { | ||||
|       [K in string & | ||||
|         `on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}` | ||||
|         ? T[Uncapitalize<C>] extends null | ||||
|           ? (...args: any[]) => any | ||||
|           : T[Uncapitalize<C>] | ||||
|         : never | ||||
|     } | ||||
|   : {} | ||||
| 
 | ||||
| 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] | ||||
|       > | ||||
|   ? (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, | ||||
|  | ||||
| @ -51,7 +51,7 @@ import { | ||||
|   ExtractPropTypes, | ||||
|   ExtractDefaultPropTypes | ||||
| } from './componentProps' | ||||
| import { EmitsOptions } from './componentEmits' | ||||
| import { EmitsOptions, EmitsToProps } from './componentEmits' | ||||
| import { Directive } from './directives' | ||||
| import { | ||||
|   CreateComponentPublicInstance, | ||||
| @ -91,16 +91,18 @@ export interface ComponentCustomOptions {} | ||||
| export type RenderFunction = () => VNodeChild | ||||
| 
 | ||||
| type ExtractOptionProp<T> = T extends ComponentOptionsBase< | ||||
|   infer P, | ||||
|   any, | ||||
|   any, | ||||
|   any, | ||||
|   any, | ||||
|   any, | ||||
|   any, | ||||
|   any | ||||
|   infer P, // Props
 | ||||
|   any, // RawBindings
 | ||||
|   any, // D
 | ||||
|   any, // C
 | ||||
|   any, // M
 | ||||
|   any, // Mixin
 | ||||
|   any, // Extends
 | ||||
|   any // EmitsOptions
 | ||||
| > | ||||
|   ? unknown extends P ? {} : P | ||||
|   ? unknown extends P | ||||
|     ? {} | ||||
|     : P | ||||
|   : {} | ||||
| 
 | ||||
| export interface ComponentOptionsBase< | ||||
| @ -114,8 +116,7 @@ export interface ComponentOptionsBase< | ||||
|   E extends EmitsOptions, | ||||
|   EE extends string = string, | ||||
|   Defaults = {} | ||||
| > | ||||
|   extends LegacyOptions<Props, D, C, M, Mixin, Extends>, | ||||
| > extends LegacyOptions<Props, D, C, M, Mixin, Extends>, | ||||
|     ComponentInternalOptions, | ||||
|     ComponentCustomOptions { | ||||
|   setup?: ( | ||||
| @ -220,9 +221,10 @@ export type ComponentOptionsWithoutProps< | ||||
|   Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, | ||||
|   Extends extends ComponentOptionsMixin = ComponentOptionsMixin, | ||||
|   E extends EmitsOptions = EmitsOptions, | ||||
|   EE extends string = string | ||||
|   EE extends string = string, | ||||
|   PE = Props & EmitsToProps<E> | ||||
| > = ComponentOptionsBase< | ||||
|   Props, | ||||
|   PE, | ||||
|   RawBindings, | ||||
|   D, | ||||
|   C, | ||||
| @ -235,7 +237,7 @@ export type ComponentOptionsWithoutProps< | ||||
| > & { | ||||
|   props?: undefined | ||||
| } & ThisType< | ||||
|     CreateComponentPublicInstance<{}, RawBindings, D, C, M, Mixin, Extends, E> | ||||
|     CreateComponentPublicInstance<PE, RawBindings, D, C, M, Mixin, Extends, E> | ||||
|   > | ||||
| 
 | ||||
| export type ComponentOptionsWithArrayProps< | ||||
| @ -248,7 +250,7 @@ export type ComponentOptionsWithArrayProps< | ||||
|   Extends extends ComponentOptionsMixin = ComponentOptionsMixin, | ||||
|   E extends EmitsOptions = EmitsOptions, | ||||
|   EE extends string = string, | ||||
|   Props = Readonly<{ [key in PropNames]?: any }> | ||||
|   Props = Readonly<{ [key in PropNames]?: any }> & EmitsToProps<E> | ||||
| > = ComponentOptionsBase< | ||||
|   Props, | ||||
|   RawBindings, | ||||
| @ -285,7 +287,7 @@ export type ComponentOptionsWithObjectProps< | ||||
|   Extends extends ComponentOptionsMixin = ComponentOptionsMixin, | ||||
|   E extends EmitsOptions = EmitsOptions, | ||||
|   EE extends string = string, | ||||
|   Props = Readonly<ExtractPropTypes<PropsOptions>>, | ||||
|   Props = Readonly<ExtractPropTypes<PropsOptions>> & EmitsToProps<E>, | ||||
|   Defaults = ExtractDefaultPropTypes<PropsOptions> | ||||
| > = ComponentOptionsBase< | ||||
|   Props, | ||||
| @ -365,7 +367,9 @@ export interface MethodOptions { | ||||
| export type ExtractComputedReturns<T extends any> = { | ||||
|   [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn } | ||||
|     ? TReturn | ||||
|     : T[key] extends (...args: any[]) => infer TReturn ? TReturn : never | ||||
|     : T[key] extends (...args: any[]) => infer TReturn | ||||
|     ? TReturn | ||||
|     : never | ||||
| } | ||||
| 
 | ||||
| export type ObjectWatchOptionItem = { | ||||
| @ -471,7 +475,7 @@ interface LegacyOptions< | ||||
|   __differentiator?: keyof D | keyof C | keyof M | ||||
| } | ||||
| 
 | ||||
| type MergedHook<T = (() => void)> = T | T[] | ||||
| type MergedHook<T = () => void> = T | T[] | ||||
| 
 | ||||
| export type MergedComponentOptions = ComponentOptions & | ||||
|   MergedComponentOptionsOverride | ||||
| @ -679,8 +683,8 @@ export function applyOptions(instance: ComponentInternalInstance) { | ||||
|       const get = isFunction(opt) | ||||
|         ? opt.bind(publicThis, publicThis) | ||||
|         : isFunction(opt.get) | ||||
|           ? opt.get.bind(publicThis, publicThis) | ||||
|           : NOOP | ||||
|         ? opt.get.bind(publicThis, publicThis) | ||||
|         : NOOP | ||||
|       if (__DEV__ && get === NOOP) { | ||||
|         warn(`Computed property "${key}" has no getter.`) | ||||
|       } | ||||
| @ -688,12 +692,12 @@ export function applyOptions(instance: ComponentInternalInstance) { | ||||
|         !isFunction(opt) && isFunction(opt.set) | ||||
|           ? opt.set.bind(publicThis) | ||||
|           : __DEV__ | ||||
|             ? () => { | ||||
|                 warn( | ||||
|                   `Write operation failed: computed property "${key}" is readonly.` | ||||
|                 ) | ||||
|               } | ||||
|             : NOOP | ||||
|           ? () => { | ||||
|               warn( | ||||
|                 `Write operation failed: computed property "${key}" is readonly.` | ||||
|               ) | ||||
|             } | ||||
|           : NOOP | ||||
|       const c = computed({ | ||||
|         get, | ||||
|         set | ||||
| @ -1006,10 +1010,11 @@ function mergeDataFn(to: any, from: any) { | ||||
|     return from | ||||
|   } | ||||
|   return function mergedDataFn(this: ComponentPublicInstance) { | ||||
|     return (__COMPAT__ && | ||||
|     isCompatEnabled(DeprecationTypes.OPTIONS_DATA_MERGE, null) | ||||
|       ? deepMergeData | ||||
|       : extend)( | ||||
|     return ( | ||||
|       __COMPAT__ && isCompatEnabled(DeprecationTypes.OPTIONS_DATA_MERGE, null) | ||||
|         ? deepMergeData | ||||
|         : extend | ||||
|     )( | ||||
|       isFunction(to) ? to.call(this, this) : to, | ||||
|       isFunction(from) ? from.call(this, this) : from | ||||
|     ) | ||||
|  | ||||
| @ -469,6 +469,7 @@ describe('type inference w/ options API', () => { | ||||
| 
 | ||||
| describe('with mixins', () => { | ||||
|   const MixinA = defineComponent({ | ||||
|     emits: ['bar'], | ||||
|     props: { | ||||
|       aP1: { | ||||
|         type: String, | ||||
| @ -523,6 +524,7 @@ describe('with mixins', () => { | ||||
|   }) | ||||
|   const MyComponent = defineComponent({ | ||||
|     mixins: [MixinA, MixinB, MixinC, MixinD], | ||||
|     emits: ['click'], | ||||
|     props: { | ||||
|       // required should make property non-void
 | ||||
|       z: { | ||||
| @ -552,6 +554,9 @@ describe('with mixins', () => { | ||||
|     setup(props) { | ||||
|       expectType<string>(props.z) | ||||
|       // props
 | ||||
|       expectType<((...args: any[]) => any) | undefined>(props.onClick) | ||||
|       // from Base
 | ||||
|       expectType<((...args: any[]) => any) | undefined>(props.onBar) | ||||
|       expectType<string>(props.aP1) | ||||
|       expectType<boolean | undefined>(props.aP2) | ||||
|       expectType<any>(props.bP1) | ||||
| @ -561,6 +566,9 @@ describe('with mixins', () => { | ||||
|     render() { | ||||
|       const props = this.$props | ||||
|       // props
 | ||||
|       expectType<((...args: any[]) => any) | undefined>(props.onClick) | ||||
|       // from Base
 | ||||
|       expectType<((...args: any[]) => any) | undefined>(props.onBar) | ||||
|       expectType<string>(props.aP1) | ||||
|       expectType<boolean | undefined>(props.aP2) | ||||
|       expectType<any>(props.bP1) | ||||
| @ -688,6 +696,7 @@ describe('with extends', () => { | ||||
| 
 | ||||
| describe('extends with mixins', () => { | ||||
|   const Mixin = defineComponent({ | ||||
|     emits: ['bar'], | ||||
|     props: { | ||||
|       mP1: { | ||||
|         type: String, | ||||
| @ -706,6 +715,7 @@ describe('extends with mixins', () => { | ||||
|     } | ||||
|   }) | ||||
|   const Base = defineComponent({ | ||||
|     emits: ['foo'], | ||||
|     props: { | ||||
|       p1: Boolean, | ||||
|       p2: { | ||||
| @ -731,6 +741,7 @@ describe('extends with mixins', () => { | ||||
|   const MyComponent = defineComponent({ | ||||
|     extends: Base, | ||||
|     mixins: [Mixin], | ||||
|     emits: ['click'], | ||||
|     props: { | ||||
|       // required should make property non-void
 | ||||
|       z: { | ||||
| @ -741,6 +752,11 @@ describe('extends with mixins', () => { | ||||
|     render() { | ||||
|       const props = this.$props | ||||
|       // props
 | ||||
|       expectType<((...args: any[]) => any) | undefined>(props.onClick) | ||||
|       // from Mixin
 | ||||
|       expectType<((...args: any[]) => any) | undefined>(props.onBar) | ||||
|       // from Base
 | ||||
|       expectType<((...args: any[]) => any) | undefined>(props.onFoo) | ||||
|       expectType<boolean | undefined>(props.p1) | ||||
|       expectType<number>(props.p2) | ||||
|       expectType<string>(props.z) | ||||
| @ -879,6 +895,8 @@ describe('emits', () => { | ||||
|       input: (b: string) => b.length > 1 | ||||
|     }, | ||||
|     setup(props, { emit }) { | ||||
|       expectType<((n: number) => boolean) | undefined>(props.onClick) | ||||
|       expectType<((b: string) => boolean) | undefined>(props.onInput) | ||||
|       emit('click', 1) | ||||
|       emit('input', 'foo') | ||||
|       //  @ts-expect-error
 | ||||
| @ -931,6 +949,8 @@ describe('emits', () => { | ||||
|   defineComponent({ | ||||
|     emits: ['foo', 'bar'], | ||||
|     setup(props, { emit }) { | ||||
|       expectType<((...args: any[]) => any) | undefined>(props.onFoo) | ||||
|       expectType<((...args: any[]) => any) | undefined>(props.onBar) | ||||
|       emit('foo') | ||||
|       emit('foo', 123) | ||||
|       emit('bar') | ||||
| @ -972,10 +992,9 @@ describe('emits', () => { | ||||
| }) | ||||
| 
 | ||||
| describe('componentOptions setup should be `SetupContext`', () => { | ||||
|   expect<ComponentOptions['setup']>({} as ( | ||||
|     props: Record<string, any>, | ||||
|     ctx: SetupContext | ||||
|   ) => any) | ||||
|   expect<ComponentOptions['setup']>( | ||||
|     {} as (props: Record<string, any>, ctx: SetupContext) => any | ||||
|   ) | ||||
| }) | ||||
| 
 | ||||
| describe('extract instance type', () => { | ||||
|  | ||||
| @ -5630,10 +5630,10 @@ prelude-ls@~1.1.2: | ||||
|   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" | ||||
|   integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= | ||||
| 
 | ||||
| prettier@~1.14.0: | ||||
|   version "1.14.3" | ||||
|   resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.3.tgz#90238dd4c0684b7edce5f83b0fb7328e48bd0895" | ||||
|   integrity sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg== | ||||
| prettier@^2.3.1: | ||||
|   version "2.3.1" | ||||
|   resolved "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz#76903c3f8c4449bc9ac597acefa24dc5ad4cbea6" | ||||
|   integrity sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA== | ||||
| 
 | ||||
| pretty-format@^26.0.0, pretty-format@^26.6.2: | ||||
|   version "26.6.2" | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user