将字符串缩小为字符串文字联合

25

我希望将一个字符串缩小到一个字符串文字联合类型。换句话说,我想检查该字符串是否是我的文字联合类型的可能值之一,以便这将起作用(如果操作符couldbe存在)。

type lit = "A" | "B" | "C";
let uni: lit;
let str = "B";
if(str couldbe lit){
    uni = str;
} else {
    doSomething(str);
}

我该如何实现这个目标?

我尝试使用 if (str instanceof lit), 但是那好像不起作用。使用 keyof 迭代字符串联合类型也无法解决,因为允许的值并不是真正的键。

一种方法是使用switch,针对每个可能的值设置一个case,但如果lit的允许值发生变化,则可能会导致细微的错误。


1
类型“lit”在运行时不存在,因此您不能像那样使用它。也许可以使用枚举代替? - Nitzan Tomer
关于 switch 语句的注释,请参见此 [答案](https://dev59.com/7FkS5IYBdhLWcg3w05aT)。 - David Sherret
@NitzanTomer 这实际上是一个非常好的想法,看起来更加清晰易懂。 - iFreilicht
4个回答

17

如果你像我一样讨厌switch case语句:
自从TypeScript 3.4 – const assertions,现在也可以从字符串数组中生成联合类型 ^_^

const lits = <const>["A", "B", "C"];
type Lit = typeof list[number]; // "A" | "B" | "C"

function isLit(str: string): str is Lit {
  return !!lits.find((lit) => str === lit);
}

7
您可以使用用户自定义类型保护来实现。
type lit = "A" | "B" | "C";
let uni: lit;
let str = "B";

function isLit(str: string): str is lit {
    return str == "A" || str == "B" || str == "C";
}
function doSomething(str: string) {

}

if (isLit(str)) {
    uni = str;
}
else {
    doSomething(str);
}

新增:

为避免重复编辑,class 可用于编译时和运行时。现在你只需要编辑一个地方。

class Lit {
    constructor(public A = 0, public B = 0, public C = 0) {}
}
type lit = keyof Lit;
let uni: lit;

function isLit(str: string): str is lit {
    let lit = new Lit();
    return (str in lit) ? true : false;
}

嗯,这比 switch 语句好一点,但仍存在可能忘记在更改 lit 允许的值时更新类型保护的问题。 - iFreilicht
我在我的答案中添加了另一种解决方案。 - thatseeyou
如果一定要使用文字联合,我非常喜欢你的第二个解决方案。不过,似乎更好的方法是切换到枚举。 - iFreilicht

3
您也可以使用一个 zod 枚举来实现这个功能:
import zod from 'zod'

const ColorMode = zod.enum(['light', 'dark', 'system'] as const)

let _mode = 'light' // type is string
let mode = ColorMode.parse(_mode) // type is "light" | "dark" | "system"

_mode = 'twilight'
mode = ColorMode.parse(_mode) // throws an error, not a valid value

您也可以在需要时从zod模式中提取类型:
type ColorMode = zod.infer<typeof ColorMode>

我发现像这样的验证库是解析、验证和类型缩小变量/数据最简单、最可靠的方式,否则我就得手动编写容易出错的type guards/predicates

3
这是我对类型保护问题的看法,以及在关闭strictNullChecks时的限制(这是项目上的限制;如果此选项为true,则TS将要求在switch/case上进行详尽性检查)。
代码行const _notLit: never = maybeLit;确保更改lit类型时,您需要同时更新switch/case
这种解决方案的缺点是,随着联合类型lit的增长,它变得非常冗长。
type lit = "A" | "B" | "C";

function isLit(str: string): str is lit {
  const maybeLit = str as lit;
  switch (maybeLit) {
    case "A":
    case "B":
    case "C":
      return true;
  }

  // assure exhaustiveness of the switch/case
  const _notLit: never = maybeLit;

  return false;
}

如果可能的话,这个任务更适合使用枚举,或者如果你需要一个类型并且不介意创建底层枚举进行检查,你可以创建类似于以下内容的类型保护:

enum litEnum {
  "A",
  "B",
  "C",
}
type lit = keyof typeof litEnum;

function isLit(str: string): str is lit {
  return litEnum[str] !== undefined;
}

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