wip: improve props typing

This commit is contained in:
Evan You
2019-06-01 00:47:05 +08:00
parent c0c06813a7
commit da5edd3429
4 changed files with 118 additions and 46 deletions

View File

@@ -13,7 +13,7 @@ import { Slots } from './componentSlots'
export type Data = { [key: string]: any }
export type ComponentPublicProperties<P = Data, S = Data> = {
export type ComponentPublicProperties<P = {}, S = {}> = {
$state: S
$props: P
$attrs: Data
@@ -27,16 +27,16 @@ export type ComponentPublicProperties<P = Data, S = Data> = {
} & P &
S
export interface ComponentOptions<
interface ComponentOptions<
RawProps = ComponentPropsOptions,
RawBindings = Data | void,
RawBindings = Data,
Props = ExtractPropTypes<RawProps>,
Bindings = UnwrapValue<RawBindings>
ExposedProps = RawProps extends object ? Props : {}
> {
props?: RawProps
setup?: (props: Props) => RawBindings
render?: <State extends Bindings>(
this: ComponentPublicProperties<Props, State>,
setup?: (this: ComponentPublicProperties, props: Props) => RawBindings
render?: <State extends UnwrapValue<RawBindings>>(
this: ComponentPublicProperties<ExposedProps, State>,
ctx: ComponentInstance<Props, State>
) => VNodeChild
}
@@ -81,16 +81,11 @@ export type ComponentInstance<P = Data, S = Data> = {
} & LifecycleHooks
// no-op, for type inference only
export function createComponent<
RawProps,
RawBindings,
Props = ExtractPropTypes<RawProps>,
Bindings = UnwrapValue<RawBindings>
>(
options: ComponentOptions<RawProps, RawBindings, Props, Bindings>
export function createComponent<RawProps, RawBindings>(
options: ComponentOptions<RawProps, RawBindings>
): {
// for TSX
new (): { $props: Props }
new (): { $props: ExtractPropTypes<RawProps> }
} {
return options as any
}

View File

@@ -13,14 +13,10 @@ import { warn } from './warning'
import { Data, ComponentInstance } from './component'
export type ComponentPropsOptions<P = Data> = {
[K in keyof P]: PropValidator<P[K]>
[K in keyof P]: Prop<P[K]> | null
}
type Prop<T> = { (): T } | { new (...args: any[]): T & object }
type PropType<T> = Prop<T> | Prop<T>[]
type PropValidator<T> = PropOptions<T> | PropType<T>
type Prop<T> = PropOptions<T> | PropType<T>
interface PropOptions<T = any> {
type?: PropType<T> | true | null
@@ -29,23 +25,42 @@ interface PropOptions<T = any> {
validator?(value: any): boolean
}
export type ExtractPropTypes<PropOptions> = {
readonly [key in keyof PropOptions]: PropOptions[key] extends PropValidator<
infer V
>
? V
: PropOptions[key] extends null | true ? any : PropOptions[key]
}
export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
type PropConstructor<T> = { new (...args: any[]): T & object } | { (): T }
type RequiredKeys<T> = {
[K in keyof T]: T[K] extends { required: true } | { default: any } ? K : never
}[keyof T]
type OptionalKeys<T> = Exclude<keyof T, RequiredKeys<T>>
type InferPropType<T> = T extends null
? any
: // null & true would fail to infer
T extends { type: null | true }
? any
: // somehow `ObjectContructor` when inferred from { (): T } becomes `any`
T extends ObjectConstructor | { type: ObjectConstructor }
? { [key: string]: any }
: T extends Prop<infer V> ? V : T
export type ExtractPropTypes<O> = O extends object
? { readonly [K in RequiredKeys<O>]: InferPropType<O[K]> } &
{ readonly [K in OptionalKeys<O>]?: InferPropType<O[K]> }
: { [K in string]: any }
const enum BooleanFlags {
shouldCast = '1',
shouldCastTrue = '2'
}
type NormalizedProp = PropOptions & {
[BooleanFlags.shouldCast]?: boolean
[BooleanFlags.shouldCastTrue]?: boolean
}
type NormalizedProp =
| null
| (PropOptions & {
[BooleanFlags.shouldCast]?: boolean
[BooleanFlags.shouldCastTrue]?: boolean
})
type NormalizedPropsOptions = Record<string, NormalizedProp>
@@ -181,14 +196,13 @@ function normalizePropsOptions(
const normalizedKey = camelize(key)
if (!isReservedKey(normalizedKey)) {
const opt = raw[key]
const prop = (normalized[normalizedKey] =
const prop: NormalizedProp = (normalized[normalizedKey] =
isArray(opt) || isFunction(opt) ? { type: opt } : opt)
if (prop) {
if (prop != null) {
const booleanIndex = getTypeIndex(Boolean, prop.type)
const stringIndex = getTypeIndex(String, prop.type)
;(prop as NormalizedProp)[BooleanFlags.shouldCast] = booleanIndex > -1
;(prop as NormalizedProp)[BooleanFlags.shouldCastTrue] =
booleanIndex < stringIndex
prop[BooleanFlags.shouldCast] = booleanIndex > -1
prop[BooleanFlags.shouldCastTrue] = booleanIndex < stringIndex
}
} else if (__DEV__) {
warn(`Invalid prop name: "${normalizedKey}" is a reserved property.`)
@@ -270,7 +284,7 @@ function validateProp(
const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/
function assertType(value: any, type: Prop<any>): AssertionResult {
function assertType(value: any, type: PropConstructor<any>): AssertionResult {
let valid
const expectedType = getType(type)
if (simpleCheckRE.test(expectedType)) {

View File

@@ -15,7 +15,7 @@ export {
export { Slot, Slots } from './componentSlots'
export { ComponentPropsOptions } from './componentProps'
export { PropType, ComponentPropsOptions } from './componentProps'
export * from './reactivity'
export * from './componentLifecycle'