types: improve h() and TSX type inference

- Should allow extraneous props as attrs in TSX
- Should check props when using constructor type returned by
  createComponent() in h()
This commit is contained in:
Evan You 2019-10-31 12:43:05 -04:00
parent b114cdf6ee
commit 3a6dcd3aba
4 changed files with 48 additions and 16 deletions

View File

@ -67,7 +67,14 @@ test('createComponent type inference', () => {
} }
}) })
// test TSX props inference // test TSX props inference
;<MyComponent a={1} b="foo" dd={['foo']} ddd={['foo']} /> ;<MyComponent
a={1}
b="foo"
dd={['foo']}
ddd={['foo']}
// should allow extraneous as attrs
class="bar"
/>
}) })
test('type inference w/ optional props declaration', () => { test('type inference w/ optional props declaration', () => {

View File

@ -9,13 +9,31 @@ import { SetupContext, RenderFunction } from './component'
import { ComponentPublicInstance } from './componentProxy' import { ComponentPublicInstance } from './componentProxy'
import { ExtractPropTypes } from './componentProps' import { ExtractPropTypes } from './componentProps'
import { isFunction } from '@vue/shared' import { isFunction } from '@vue/shared'
import { Ref } from '@vue/reactivity'
interface BaseProps {
[key: string]: any
key?: string | number
ref?: string | Ref | Function
}
// overload 1: direct setup function // overload 1: direct setup function
// (uses user defined props interface) // (uses user defined props interface)
// __isConstructor: true is a type-only differentiator to avoid returned
// constructor type from being matched as an options object in h()
export function createComponent<Props, RawBindings = object>( export function createComponent<Props, RawBindings = object>(
setup: (props: Props, ctx: SetupContext) => RawBindings | RenderFunction setup: (props: Props, ctx: SetupContext) => RawBindings | RenderFunction
): { ): {
new (): ComponentPublicInstance<Props, RawBindings> __isConstructor: true
new (): ComponentPublicInstance<
Props,
RawBindings,
{},
{},
{},
// public props
BaseProps & Props
>
} }
// overload 2: object format with no props // overload 2: object format with no props
@ -30,11 +48,19 @@ export function createComponent<
>( >(
options: ComponentOptionsWithoutProps<Props, RawBindings, D, C, M> options: ComponentOptionsWithoutProps<Props, RawBindings, D, C, M>
): { ): {
new (): ComponentPublicInstance<Props, RawBindings, D, C, M> __isConstructor: true
new (): ComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
BaseProps & Props
>
} }
// overload 3: object format with array props declaration // overload 3: object format with array props declaration
// props inferred as { [key in PropNames]?: unknown } // props inferred as { [key in PropNames]?: any }
// return type is for Vetur and TSX support // return type is for Vetur and TSX support
export function createComponent< export function createComponent<
PropNames extends string, PropNames extends string,
@ -45,13 +71,9 @@ export function createComponent<
>( >(
options: ComponentOptionsWithArrayProps<PropNames, RawBindings, D, C, M> options: ComponentOptionsWithArrayProps<PropNames, RawBindings, D, C, M>
): { ): {
new (): ComponentPublicInstance< __isConstructor: true
{ [key in PropNames]?: unknown }, // array props technically doesn't place any contraints on props in TSX
RawBindings, new (): ComponentPublicInstance<BaseProps, RawBindings, D, C, M>
D,
C,
M
>
} }
// overload 4: object format with object props declaration // overload 4: object format with object props declaration
@ -65,14 +87,15 @@ export function createComponent<
>( >(
options: ComponentOptionsWithObjectProps<PropsOptions, RawBindings, D, C, M> options: ComponentOptionsWithObjectProps<PropsOptions, RawBindings, D, C, M>
): { ): {
__isConstructor: true
// for Vetur and TSX support // for Vetur and TSX support
new (): ComponentPublicInstance< new (): ComponentPublicInstance<
ExtractPropTypes<PropsOptions>, ExtractPropTypes<PropsOptions, false>,
RawBindings, RawBindings,
D, D,
C, C,
M, M,
ExtractPropTypes<PropsOptions, false> BaseProps & ExtractPropTypes<PropsOptions, false>
> >
} }

View File

@ -65,6 +65,10 @@ export interface ComponentOptionsBase<
components?: Record<string, Component> components?: Record<string, Component>
directives?: Record<string, Directive> directives?: Record<string, Directive>
inheritAttrs?: boolean inheritAttrs?: boolean
// type-only differentiator to separate OptionWihtoutProps from a constructor
// type returned by createComponent()
__isConstructor?: never
} }
export type ComponentOptionsWithoutProps< export type ComponentOptionsWithoutProps<

View File

@ -133,9 +133,7 @@ export function h<P>(
): VNode ): VNode
export function h<P extends string>( export function h<P extends string>(
type: ComponentOptionsWithArrayProps<P>, type: ComponentOptionsWithArrayProps<P>,
// TODO for now this doesn't really do anything, but it would become useful props?: RawProps | null,
// if we make props required by default
props?: (RawProps & { [key in P]?: any }) | null,
children?: RawChildren | RawSlots children?: RawChildren | RawSlots
): VNode ): VNode
export function h<P>( export function h<P>(