如何在不使用类型断言的情况下检查字符串是否包含在字符串字面类型中?

3

我经常拥有类似这样的字符串字面量类型:

const supportedLanguageTags = [
    "de-at",
    "de-ch",
    "de-de",
    "en-gb",
    "en-us",
] as const;

type LanguageTag = typeof supportedLanguageTags[number];

function isSupportedLanguageTag(tag: string): tag is LanguageTag {
    return (supportedLanguageTags as unknown as string[]).includes(tag);
}

我在这里使用数组来定义类型,同时也为了能够检查随机字符串是否包含在字面类型中。

但是,我真的不喜欢类型断言。你有什么建议来摆脱它吗?

一个解决方案是使用对象而不是数组,像这样:

const supportedLanguageTags = {
    "de-at": 1,
    "de-ch": 1,
    "de-de": 1,
    "en-gb": 1,
    "en-us": 1,
};

type LanguageTag = keyof typeof supportedLanguageTags;

function isSupportedLanguageTag(tag: string): tag is LanguageTag {
    return tag in supportedLanguageTags;
}

但是我不喜欢为每个对象属性定义一个随机值。


如果想保持类型安全,您可以将supportedLanguageTags扩展为 readonly string[] 而不需要进行类型断言,并且需要声明一个新变量的成本...就像 这样。如果符合您的要求,我很乐意撰写答案。如果不行,请详细介绍问题所在。此外,这个问答是否解决了您的问题? - jcalz
1
@jcalz 谢谢。是的,那个 Q/A 是相同的情况。使用 readonly string[] 已经是一种改进了,所以请随意发布一个答案。 - Krisztián Balla
2个回答

7
类似问题的答案中所述, TypeScript 中 Array.prototype.includes() 的类型定义要求搜索值与数组元素类型相同。如果数组元素是string类型,则可以搜索任何string。但如果数组元素是某些比string类型更狭窄的类型,例如包含字符串字面量类型联合类型,则将无法搜索任意string。这种限制实际上并不是必要的类型安全措施;您应该能够安全地搜索比元素类型宽泛的内容。
问题microsoft/TypeScript#26255被提出,要求在这里提供支持,但它被关闭为microsoft/TypeScript#14520的重复项,该项涉及表示超类型约束的一种方式。TypeScript仅有extends,其中U extends T表示U必须是T的某个子类型。对于Array<T>,您实际上需要类似于includes<U super T>(searchElement: U): boolean的东西,其中U super T表示U必须是T的某个超类型。然而,TypeScript没有这样的语法。您可以使用条件类型模拟它,例如includes<U extends (T extends U ? unknown : never)>(searchElement: U),但这相当复杂,目前不是TypeScript类型定义的一部分。

你可以在这里执行的一件事是将数组类型安全地扩展为readonly string[]readonly数组类型是string的超类型,不会有像push()这样的变异方法。字符串文字数组可以安全地扩展为readonly string[],因为您不会修改其内容,并且始终更安全地将任何字符串文字类型的值读入期望string的内容。

类型断言允许(安全的)扩展和(不安全的)缩小。但是,如果您想确保没有意外进行了不安全的缩小,则可以放弃类型断言,而是注释一个新变量。类型注释仅支持安全扩展。

这意味着您可以编写以下内容:
function isSupportedLanguageTag(tag: string): tag is LanguageTag {
  const s: readonly string[] = supportedLanguageTags; // okay
  return s.includes(tag); // okay
}

你可以拥有类型安全。同样,s变量并不是必要的,如果你更注重简洁而非安全保证,那么类型断言可以将代码简化为更简洁的形式return (supportedLanguageTage as readonly string[]).includes(tag);

代码的游乐场链接


1

现在已知Array.prototype.includes存在问题,但我找不到相关链接。 为了欺骗TypeScript,您可以重载函数。由于重载可以双向工作,TS将允许您使用tag is LanguageTag返回类型:

const supportedLanguageTags = [
  "de-at",
  "de-ch",
  "de-de",
  "en-gb",
  "en-us",
] as const;

type LanguageTag = typeof supportedLanguageTags[number];

function isSupported(tag: string): tag is LanguageTag
function isSupported(tag: any) {
  return supportedLanguageTags.includes(tag)
}


isSupported('hello') // ok
isSupported(42) // error

游乐场

是的,我使用了any,但你不能使用数字调用isSupported,只能使用string


我恐怕必须禁用我们的代码检查器,因为这一行使用了 any,但我认为这比使用类型断言更好。 - Krisztián Balla

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