I have this types:
type IInputType = "text" | "checkbox" | "number";
type IField<T extends IInputType> = {
name: string;
value:
T extends "checkbox" ? boolean :
T extends "number" ? number :
string;
type: T;
};
type IPossibleFieldTypes = IField<IInputType>;
That way the conditional is not working because field.value is typed as string, number or boolean, and not only boolean as should:
// field type is IPossibleFieldTypes
if (field.type === "checkbox") {
// Here field.value should only be boolean
// but TS are allowing string or number too
}
But if I specify the possible IFields manually:
type IPossibleFieldTypes =
| IField<"text">
| IField<"checkbox">
| IField<"number">;
Now the types are being displayed correctly:
if (field.type === "checkbox") {
// Here field.value can only be boolean
}
The question is:
There is a way to make this type work (IPossibleFieldTypes) without have to manually set the possible generics?
CodePudding user response:
Make IField
a distributive conditional type, so that you make a discriminated union:
type IInputType = keyof ValueMap;
type ValueMap = {
text: string;
checkbox: boolean;
number: number;
};
type IField<T extends IInputType> = T extends T ? {
name: string;
value: ValueMap[T];
type: T;
} : never;
Also, we take advantage of the fact that T
is a string, which means instead of a long chain of conditional types, we can use another type that maps T
to the right type. Then narrowing will work correctly:
if (field.type === "checkbox") {
field.value;
// ^? boolean
}
CodePudding user response:
You have wrong logic in value
, where T extends IInputType
which is with every type is true. Just do this way:
type IField<T extends IInputType> = {
name: string;
value:
"checkbox" extends T ? boolean :
"number" extends T ? number :
string;
type: T;
};
And this fix your code.