Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
320 views
in Technique[技术] by (71.8m points)

TypeScript union of string and string literals

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 
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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:

IntelliSense suggesting "red","green","blue"


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:

enter image description here


Okay, hope that helps!

Playground link to code


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...