我经常使用像这样的字符串文字类型:
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;
}
但是这里我不喜欢为每个对象属性定义一个随机值。
在一个类似问题的答案中提到,Array.prototype.includes()
调用签名的TypeScript类型要求搜索值与数组元素的类型相同。如果数组元素的类型是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);
。
Playground链接到代码
有一个已知的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
。