I'd like to create a type definition for one of known string literals, or any string, e.g:
type IColor = "blue" | "green" | "red" | string;
TS doesn't complain about that type definition, but it also doesn't help with intellisense. My goal would be to define a function that accepts one of known colors or any color string.
const knownColors = {
green: "#66bb6a",
red: "#ef9a9a",
blue: "#81d4fa",
} as const;
function getColor(color: keyof typeof knownColors | string): string {
if (color in knownColors)
return knownColors[color as keyof typeof knownColors];
return color;
}
Usage:
getColor("blue"); // "blue" is a known color, returns "#81d4fa"
getColor("black"); // "black" is not a known color, returns "black"
I want intellisense to be intelligent
getColor("_ // as I type this line,
^ blue // I want these suggestions
^ green
^ red
This is currently considered a design limitation (see microsoft/TypeScript#29729) and/or a missing feature (see microsoft/TypeScript#33471) of TypeScript. From the type system's point of view, string
is a supertype of any string literal like "blue"
or "red"
, and so a union string | "blue" | "red"
is the same as string
and the compiler aggressively reduces such unions to string
. This is completely correct as far as type safety goes. But it's not great from the point of view of documentation or IntelliSense, as you've seen.
Luckily the linked TypeScript issues suggest some workarounds which you might find useful. One is to represent the union type in a way that the compiler does not aggressively reduce. The type string & {}
is conceptually the same as string
, since the empty type {}
matches any non-null
and non-undefined
type. But the compiler does not perform this reduction (at least as of TS 3.8). From this type you can build your union like (string & {}) | "red" | "green" | "blue"
, and the compiler will keep this representation long enough to give you IntelliSense hints:
function getColor(color: (keyof typeof knownColors) | (string & {})): string {
if (color in knownColors)
return knownColors[color as keyof typeof knownColors];
return color;
}
This accepts and rejects the same inputs as before:
getColor("red"); // okay
getColor("mauve"); // okay
getColor(123); // error
But you can verify that IntelliSense produces the following:
The type signature might be a little more confusing than you'd like. You could also get a similar effect by using overloads instead:
function getColorOvld(color: (keyof typeof knownColors)): string;
function getColorOvld(color: string): string;
function getColorOvld(color: string): string {
if (color in knownColors)
return knownColors[color as keyof typeof knownColors];
return color;
}
getColorOvld("red"); // okay
getColorOvld("mauve"); // okay
getColorOvld(123); // error
This also gives you reasonable IntelliSense:
Okay, hope that helps!
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