I want to write a type 'PROP' for this to work
let a : PROP = {
type: String,
default: 'STR'
} // OK
let a : PROP = {
type: String,
default: []
} // ERR
In general, a type in which the value of the default field will depend on the value of the field type. I tried to write
type CleanPropTypes = typeof Array | typeof Object | typeof Function | typeof Boolean | typeof String
type PROP<T = CleanPropTypes, U = any> = {
type:T,
default?: U extends T
}
var b : PROP = {
type:Array,
default:[]
}
example
But it doesn't work. How to write this type ?
The right definition for Prop should probably be a union of valid type/default pairs corresponding to each primitive wrapper object creator in CleanPropTypes. Something like this:
type Prop = {
type: ArrayConstructor;
default?: unknown[] | undefined;
} | {
type: ObjectConstructor;
default?: object | undefined;
} | {
type: FunctionConstructor;
default?: Function | undefined;
} | {
type: BooleanConstructor;
default?: boolean | undefined;
} | {
type: StringConstructor;
default?: string | undefined;
}
That behaves how you'd like:
let good1: Prop = {
type: String,
default: 'STR'
} // okay
let bad1: Prop = {
type: String,
default: []
} // error
var good2: Prop = {
type: Array,
default: []
} // okay
var bad2: Prop = {
type: Object,
default: "oops"
} // error
Now, you could manually define Prop, but if you'd like the compiler to compute Prop in terms of CleanPropTypes, you can mostly do so by treating each of those
primitive wrappers as a function that produces a value of the relevant primitive type. For example:
type Prop2 = CleanPropTypes extends infer C ?
C extends (...args: any) => infer R ?
{ type: C, default?: R }
: never : never;
Here I'm using conditional type inference twice. The first time, CleanPropTypes extends infer C ? ... : never basically just copies the CleanPropTypes specific type into the new type parameter C. Then, when we write C extends (...args: any) => infer R ? ... : never, we are getting the return type R of the function in C. The reason we do the copying first is so that C extends ... ? ... : ... becomes a distributive conditional type, breaking the CleanPropTypes union into individual elements, and evaluating { type: C, default?: R } for each such element, and then putting them back together in a union.
Anyway, this is almost what you want:
/* type Prop2 = {
type: ArrayConstructor;
default?: unknown[] | undefined;
} | {
type: ObjectConstructor;
default?: any;
} | {
type: FunctionConstructor;
default?: Function | undefined;
} | {
type: BooleanConstructor;
default?: boolean | undefined;
} | {
type: StringConstructor;
default?: string | undefined;
} */
Everything is correct except for the ObjectContructor element. Here, the default property is of the any type which allows anything, except for the more correct object type which only allows non-primitives. I assume the call signature for Object predates the introduction of object. See ms/TS#13741 for some discussion about this.
Anyway, since that one isn't working for us, we can do just that one manually, and then produce the rest from Exclude<CleanPropTypes, ObjectConstructor>, where we use the Exclude<T, U> utility type to remove ObjectConstructor from the union:
type Prop3 = (Exclude<CleanPropTypes, typeof Object> extends infer C ?
C extends (...args: any) => infer I ?
{ type: C, default?: I } : never : never
) | { type: ObjectConstructor, default?: object }
And that produces the same type as Prop above.
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With