Skip to content

Commit

Permalink
fix: defineComponent() with array props (#364)
Browse files Browse the repository at this point in the history
* fix: defineComponent() with array props

* fix

* fix by @pikax

* add test

* update test

* fix
  • Loading branch information
antfu committed Jun 10, 2020
1 parent bca3a69 commit d7048d4
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 38 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"rollup-plugin-terser": "^6.1.0",
"rollup-plugin-typescript2": "^0.27.1",
"ts-jest": "^26.1.0",
"typescript": "^3.9.3",
"typescript": "^3.9.5",
"vue": "^2.6.11",
"vue-router": "^3.3.2",
"vue-server-renderer": "^2.6.11"
Expand Down
40 changes: 37 additions & 3 deletions src/component/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ interface ComponentOptionsWithProps<
setup?: SetupFunction<Props, RawBindings>;
}

interface ComponentOptionsWithArrayProps<
PropNames extends string = string,
RawBindings = Data,
Props = Readonly<{ [key in PropNames]?: any }>
> {
props?: PropNames[];
setup?: SetupFunction<Props, RawBindings>;
}

interface ComponentOptionsWithoutProps<Props = unknown, RawBindings = Data> {
props?: undefined;
setup?: SetupFunction<Props, RawBindings>;
Expand All @@ -74,7 +83,20 @@ interface ComponentOptionsWithoutProps<Props = unknown, RawBindings = Data> {
export function defineComponent<RawBindings>(
options: ComponentOptionsWithoutProps<unknown, RawBindings>
): VueProxy<unknown, RawBindings>;
// overload 2: object format with object props declaration
// overload 2: object format with array props declaration
// props inferred as { [key in PropNames]?: any }
// return type is for Vetur and TSX support
export function defineComponent<
PropNames extends string,
RawBindings = Data,
PropsOptions extends ComponentPropsOptions = ComponentPropsOptions
>(
// prettier-ignore
options: (
ComponentOptionsWithArrayProps<PropNames, RawBindings>) &
Omit<Vue2ComponentOptions<Vue>, keyof ComponentOptionsWithProps<never, never>>
): VueProxy<Readonly<{ [key in PropNames]?: any }>, RawBindings>;
// overload 3: object format with object props declaration
// see `ExtractPropTypes` in ./componentProps.ts
export function defineComponent<
Props,
Expand All @@ -94,12 +116,24 @@ export function defineComponent(options: any) {
return options as any;
}

// createComponent is kept around for retro-compatibility
// overload 1: object format with no props
export function createComponent<RawBindings>(
options: ComponentOptionsWithoutProps<unknown, RawBindings>
): VueProxy<unknown, RawBindings>;
// overload 2: object format with object props declaration
// overload 2: object format with array props declaration
// props inferred as { [key in PropNames]?: any }
// return type is for Vetur and TSX support
export function createComponent<
PropNames extends string,
RawBindings = Data,
PropsOptions extends ComponentPropsOptions = ComponentPropsOptions
>(
// prettier-ignore
options: (
ComponentOptionsWithArrayProps<PropNames, RawBindings>) &
Omit<Vue2ComponentOptions<Vue>, keyof ComponentOptionsWithProps<never, never>>
): VueProxy<Readonly<{ [key in PropNames]?: any }>, RawBindings>;
// overload 3: object format with object props declaration
// see `ExtractPropTypes` in ./componentProps.ts
export function createComponent<
Props,
Expand Down
52 changes: 22 additions & 30 deletions src/component/componentProps.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { Data } from './component';

export type ComponentPropsOptions<P = Data> = {
[K in keyof P]: Prop<P[K], true | false> | null;
export type ComponentPropsOptions<P = Data> = ComponentObjectPropsOptions<P> | string[];

export type ComponentObjectPropsOptions<P = Data> = {
[K in keyof P]: Prop<P[K]> | null;
};

type Prop<T, Required extends boolean> = PropOptions<T, Required> | PropType<T>;
export type Prop<T> = PropOptions<T> | PropType<T>;

type DefaultFactory<T> = () => T | null | undefined;

export interface PropOptions<T = any, Required extends boolean = false> {
type?: PropType<T> | null;
required?: Required;
default?: T | null | undefined | (() => T | null | undefined);
validator?(value: any): boolean;
export interface PropOptions<T = any> {
type?: PropType<T> | true | null;
required?: boolean;
default?: T | DefaultFactory<T> | null | undefined;
validator?(value: unknown): boolean;
}

export type PropType<T> = PropConstructor<T> | PropConstructor<T>[];
Expand All @@ -30,30 +34,18 @@ type RequiredKeys<T, MakeDefaultRequired> = {

type OptionalKeys<T, MakeDefaultRequired> = Exclude<keyof T, RequiredKeys<T, MakeDefaultRequired>>;

type ExtractFunctionPropType<
T extends Function,
TArgs extends Array<any> = any[],
TResult = any
> = T extends (...args: TArgs) => TResult ? T : never;

type ExtractCorrectPropType<T> = T extends Function
? ExtractFunctionPropType<T>
: Exclude<T, Function>;

// prettier-ignore
type InferPropType<T> = T extends null
? any // null & true would fail to infer
: T extends { type: null }
? any // somehow `ObjectConstructor` when inferred from { (): T } becomes `any`
: T extends { type: null | true }
? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
: T extends ObjectConstructor | { type: ObjectConstructor }
? { [key: string]: any }
: T extends Prop<infer V, true | false>
? ExtractCorrectPropType<V>
: T;

// prettier-ignore
export type ExtractPropTypes<O, MakeDefaultRequired extends boolean = true> = {
readonly [K in RequiredKeys<O, MakeDefaultRequired>]: InferPropType<O[K]>;
} & {
readonly [K in OptionalKeys<O, MakeDefaultRequired>]?: InferPropType<O[K]>;
};
: T extends BooleanConstructor | { type: BooleanConstructor }
? boolean
: T extends Prop<infer V> ? V : T;

export type ExtractPropTypes<O, MakeDefaultRequired extends boolean = true> = O extends object
? { [K in RequiredKeys<O, MakeDefaultRequired>]: InferPropType<O[K]> } &
{ [K in OptionalKeys<O, MakeDefaultRequired>]?: InferPropType<O[K]> }
: { [K in string]: any };
16 changes: 16 additions & 0 deletions test/types/defineComponent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,22 @@ describe('defineComponent', () => {
expect.assertions(2);
});

it('should accept tuple props', () => {
const App = defineComponent({
props: ['p1', 'p2'],
setup(props) {
props.p1;
props.p2;
type PropsType = typeof props;
type Expected = { readonly p1?: any; readonly p2?: any };
isSubType<Expected, PropsType>(true);
isSubType<PropsType, Expected>(true);
},
});
new Vue(App);
expect.assertions(2);
});

it('infer the required prop', () => {
const App = defineComponent({
props: {
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4034,10 +4034,10 @@ typedarray-to-buffer@^3.1.5:
dependencies:
is-typedarray "^1.0.0"

typescript@^3.9.3:
version "3.9.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a"
integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==
typescript@^3.9.5:
version "3.9.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36"
integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==

union-value@^1.0.0:
version "1.0.1"
Expand Down

0 comments on commit d7048d4

Please sign in to comment.