173. 标签
极难
尽管 TypeScript 中的结构化类型系统非常强大,但有时使用标签标记一些类型会更方便,以便这些标签不会干扰将这些类型的值互相赋值的能力。
例如,使用标签,您可以检查某个值是否通过了所需函数的调用,并且是按正确的顺序调用的:
const doA = <T extends string>(x: T) => {
const result = x
return result as Tag<typeof result, 'A'>
}
const doB = <T extends string>(x: T) => {
const result = x
return result as Tag<typeof result, 'B'>
};
const a = doA('foo')
const b = doB(a)
type Check0 = IsTrue<HasTags<typeof b, ['A', 'B']>>
编写一个函数 Tag<B, T extends string>
,该函数接受一个除 null
和 undefined
之外的类型 B
,并返回一个带有字符串字面量类型 T
的标记类型。
标记的类型必须与原始类型彼此可互相赋值:
declare let x: string
declare let y: Tag<string, 'A'>
x = y = x
当给已经标记过的类型添加标签时,必须将新的标签添加到标签列表的末尾:
type T0 = Tag<{ foo: string }, 'A'>
type T1 = Tag<T0, 'B'>
type Check1 = IsTrue<HasExactTags<T1, ['A', 'B']>>
添加一些函数来检查类型标签。
GetTags<B>
用于检索类型 B
所有标签的列表:
type T2 = Tag<number, 'C'>
type Check2 = IsTrue<Equal<GetTags<T2>, ['C']>>
HasTag<B, T extends string>
用于检查类型 B
是否带有标签 T
(并返回 true
或 false
):
type T3 = Tag<0 | 1, 'D'>
type Check3 = IsTrue<HasTag<T3, 'D'>>
HasTags<B, T extends readonly string[]>
用于检查类型 B
是否依次带有标签元组 T
中的标签:
type T4 = Tag<Tag<Tag<{}, 'A'>, 'B'>, 'C'>
type Check4 = IsTrue<HasTags<T4, ['B', 'C']>>
HasExactTags<B, T extends readonly string[]>
用于检查类型 B
的所有标签的列表是否严格等于标签元组 T
:
type T5 = Tag<Tag<unknown, 'A'>, 'B'>
type Check5 = IsTrue<HasExactTags<T5, ['A', 'B']>>
最后,添加类型 UnTag<B>
,该类型用于去除类型 B
所有的标签:
type T6 = Tag<{ bar: number }, 'A'>
type T7 = UnTag<T6>
type Check6 = IsFalse<HasTag<T7, 'A'>>