TypeScript 字符串和字符串字面量的联合

22

我希望创建一个类型定义,可以包含已知的字符串字面量或任何字符串,例如:

type IColor = "blue" | "green" | "red" | string;

TS 对这种类型定义不会发出警告,但它也无法帮助智能感知。我的目标是定义一个函数,该函数可以接受已知颜色或任何颜色字符串。

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;
}

使用方法:

getColor("blue");    // "blue" is a known color, returns "#81d4fa" 
getColor("black");   // "black" is not a known color, returns "black"

我希望Intellisense更加智能化。

getColor("_          // as I type this line, 
          ^ blue     // I want these suggestions
          ^ green
          ^ red 
2个回答

48

目前这被认为是 TypeScript 的设计限制(请参见 microsoft/TypeScript#29729)和/或缺失的功能(请参见 microsoft/TypeScript#33471)。从类型系统的角度来看,string 是任何字符串字面量的超类型,如 "blue""red",因此联合类型 string | "blue" | "red"string 相同,并且编译器会积极地将这种联合类型减少到 string。就类型安全性而言,这是完全正确的。但从文档或 IntelliSense 的角度来看,它并不是很好,正如您所见。

幸运的是,链接的 TypeScript 问题提供了一些解决方法,您可能会发现有用。其中之一是以编译器不积极减少的方式表示联合类型。类型 string & {} 在概念上与 string 相同,因为空类型{}匹配任何非null和非-undefined类型。但编译器不执行此减少(至少在 TS 3.8 中不执行)。从此类型,您可以构建类似于 (string & {}) | "red" | "green" | "blue" 的联合类型,编译器将保留此表示形式足够长的时间来为您提供 IntelliSense 提示:

function getColor(color: (keyof typeof knownColors) | (string & {})): string {
    if (color in knownColors)
        return knownColors[color as keyof typeof knownColors];
    return color;
}

这个接受和拒绝与之前相同的输入:

getColor("red"); // okay
getColor("mauve"); // okay
getColor(123); // error

但是您可以验证IntelliSense会生成以下内容:

IntelliSense建议“red”、“green”、“blue”


类型签名可能有点令人困惑。 您也可以使用重载来获得类似的效果:

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

这也会给您合理的 IntelliSense:

在此输入图片描述


好的,希望有所帮助!

代码的 Playground 链接


对于未来的读者,有一个名为 type-fest 的包,您可以使用 LiteralUnion 来启用自动完成:https://github.com/sindresorhus/type-fest/blob/main/source/literal-union.d.ts - konsalex
那么我假设这个操作不会产生副作用,对于库的使用是安全的? - Caleb Taylor
这似乎适用于TS 4.0+版本,有3.8的替代方案吗?https://tsplay.dev/NdvknW - Nikola-Milovic

6

提示字符串

下面是一种基于@jcalz的答案提供提示字符串功能的类型。

/**
 * Constructs a string type with type hints for known values.
 */
// eslint-disable-next-line @typescript-eslint/ban-types
type HintedString<KnownValues extends string> = (string & {}) | KnownValues;

let nodeId: HintedString<':root' | ':trash'> = ':root';

nodeId = '454v3tv3';

Playground

需要注意的是,这种"提示的字符串"不安全并且不建议在公共定义中使用,因为它将在整个代码库中使用。例如,在上面的示例中,如果我想将root重命名为其他名称,则无法从TypeScript获取任何提示。

类型安全的替代方案

对象包装器

最通用的方法是将字符串类型包装在对象或数组中,虽然看起来可能不太美观,但更加类型安全。

例如:

type Node = ':root' | ':trash' | [string]
// or
type Node = ':root' | ':trash' | { id: string }

模板字面量类型

在某些情况下,有一个更好的解决方案可用 - 模板字面量

type NodeId = `node_${string}`
type Target = ':root' | ':trash' | NodeId

let target: Target;

target = ''; // error
target = 'node_3r3rfewfwf'; // ok
target = ':root'; // ok

在这种情况下,NodeId类型的额外好处在于它比字符串只是别名更有意义,并强制进行额外的类型检查。当然,只有在特定情况下应用它才是有意义的。演示


1
我们可以使用这个吗?Key | Omit<string,Key> - Arpit Bhalla

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接