Issue
I've got a setup like so. The idea is that I want to accept a generic and an options shape, infer the options necessary from the generic and the type
key of the options shape, and constrain the options appropriately.
type OptionProp<T extends string | boolean> = {
defaultValue: T;
} & (T extends string
? { type: "select"; options: T[] } | { type: "text" }
: { type: "checkbox" });
function useFixtureOption<T extends string>(
name: string,
prop: OptionProp<T>
): T {
console.log(prop);
return prop.defaultValue;
}
useFixtureOption<"foo" | "bar">("field", {
type: "select",
options: ["foo", "bar"],
defaultValue: "foo",
});
However, Typescript is confused about my options
type, defined as T[]
, and thinks that it's "foo"[] | "bar"[]
rather than ("foo" | "bar")[]
:
If I don't use a conditional type here, everything is fine:
type OptionProp<T extends string> = {
defaultValue: T;
} & ({ type: "select"; options: T[] } | { type: "text" });
But I want to be able to enforce that a boolean T
requires type: checkbox
and that text/select require a string T.
I feel like there's some subtlety of the type system that I'm missing here, but I have no clue where it is. My best guess is that the T extends string
check is over-narrowing the type and producing a type option per member of the T union. Is that correct, and if so, how do I deal with it?
Edit: I've apparently discovered distributed conditional types. F<A|B> = F<A> | F<B>
, which makes sense as to why I'm seeing what I'm seeing now. Now, how to fix it?
Solution
I've tracked this down. I've run into a feature called distributive conditional types which takes union and distributes it, resulting in a new type F<A|B> = F<A> | F<B>
.
The fix is straightforward and from the manual:
Typically, distributivity is the desired behavior. To avoid that behavior, you can surround each side of the extends keyword with square brackets.
type OptionProp<T = any> = ([T] extends [string]
? { type: "text" } | { type: "select"; options: T[] }
: { type: "checkbox" }) & {
defaultValue: T;
value?: T;
};
Answered By - Chris Heald
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.