TS的模板字符串
TS 字符串模板类型的写法跟 JS 模板字符串非常类似,我们看官方的例子:
type World = 'world';
type Greeting = `hello ${World}`;
// type Greeting = "hello world"
除了前面的 type 跟 JS 不一样之外,后面就是一模一样了,通过 ${}
包裹,里面可以直接传入类型变量,使用变量的模板字符串才是有灵魂的。
我们可以使用模板字符串来组合联合类型:
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
// type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"
联合类型会被挨个组合到模板中,最后生成一个联合类型,当然这只是模板字符串功能的冰山一角
结合内置的字符串工具类型 Uppercase<StringType>
、Lowercase<StringType>
、Capitalize<StringType>
与 Uncapitalize<StringType>
,我们可以进一步玩出更多花样:
type World = 'world';
type Greeting = `HELLO ${Uppercase<World>}`;
// type Greeting = "HELLO WORLD"
type LowerCaseGreeting = Lowercase<Greeting>;
// type LowerCaseGreeting = "hello world"
type CapitalizeGreeting = Capitalize<LowerCaseGreeting>;
// type CapitalizeGreeting = "Hello World"
type UncapitalizeGreeting = Uncapitalize<Greeting>;
// type UncapitalizeGreeting = "hELLO WORLD"
在映射类型中使用模板字符串
如果我们想统一生成对象属性的 get 或 set 方法,可以通过模板字符串很容易实现:
// 增加 getter 方法,返回值就是对应值的类型
type AddGetter<T extends object> = {
[K in keyof T & string as `get${ Capitalize<K> }`]: () => T[K];
};
// 增加 setter 方法,参数类型就是对应值的类型
type AddSetter<T extends object> = {
[K in keyof T & string as `set${ Capitalize<K> }`]: (arg: T[K]) => void;
};
type Student = {
name: string;
age: number;
gender: 'MALE' | 'FEMAL';
};
type AddGetterAndSetter<T extends object> = T & AddGetter<T> & AddSetter<T>;
type T1 = AddGetterAndSetter<Student>;
这样 T1 就包含了 Student 中属性,并且也包含了属性的 get 和 set 方法,变量类型也一一对应上了。
结合 infer 开启字符串魔法
infer
推导类型,可谓是 TS 类型编程中的魔法核心
通过它我们能更进一步的操作字符串,我们首先热身实现 Trim
类型:
type TrimLeft<Str extends string> = Str extends ` ${ infer Rest }` ? TrimLeft<Rest> : Str;
type TrimRight<Str extends string> = Str extends `${ infer Rest } ` ? TrimRight<Rest> : Str;
type Trim<Str extends string> = TrimLeft<TrimRight<Str>>;
type T2 = Trim<' Hello World '>;
反转字符串:
type ReverseString<Str extends string> = Str extends `${ infer First }${ infer Rest }` ? `${ ReverseString<Rest> }${ First }` : Str;
type T3 = ReverseString<'abcdef'>;
// type T3 = "fedcba"
在上面的类型推导中,通过模板字符串结合 infer
取出第一个字母 First
和剩下的字符串部分 Rest
,通过递归调用将剩下的字符串继续翻转,直到无法完成模式匹配退出。
实现字符串查找替换类型:
type ReplaceAll<Str extends string, Sub extends string, NSub extends string> = Str extends `${ infer Before }${ Sub }${ infer Rest }` ? `${ Before }${ NSub }${ ReplaceAll<Rest, Sub, NSub> }` : Str;
type T4 = ReplaceAll<'h-e-l-l-o world', '-', ''>;
// type T4 = "hello world"
实现字符串切割类型:
type Split<Str extends string, Delimiter extends string> = Str extends `${ infer Left }${ Delimiter }${ infer Rest }` ? [ Left, ...Split<Rest, Delimiter> ] : Str extends Delimiter ? [] : [ Str ];
type T5 = Split<'Hello-World', '-'>;
// type T5 = ["Hello", "World"]
字符串拼接
type Join<Str extends string[], Delimiter extends string> =
字符串长度计算
利用数组的 length 我们甚至可以实现计算字符串的长度:
type StrLen<Str extends string, TArr extends unknown[] = []> = Str extends `${ string }${ infer Rest }` ? StrLen<Rest, [unknown, ...TArr]> : TArr['length'];
type T6 = StrLen<'HelloWorld!'>;
// type T6 = 11
格式化字符串
结合前面实现的反转字符串,我们还可以实现格式化数字比如"123456" => "123,456"
:
type ReverseString<Str extends string> = Str extends `${ infer First }${ infer Rest }` ? `${ ReverseString<Rest> }${ First }` : Str;
type _FormatNum<Str extends string> = Str extends `${ infer A }${ infer B }${ infer C }${ infer Rest }` ? `${ A }${ B }${ C }${ Rest extends `${ infer D }${ infer F }` ? ',' : '' }${ _FormatNum<Rest> }` : Str;
type FormatNum<Str extends string> = ReverseString<_FormatNum<ReverseString<Str>>>;
type T7 = FormatNum<'1234567890'>;
// type T7 = "1,234,567,890"
连续相同字符消消乐
type Eliminate<Str extends string> = Str extends `${ infer A }${ infer B }${ infer Rest }` ? A extends B ? Eliminate<`${ B }${ Rest }`> : `${ A }${ Eliminate<`${ B }${ Rest }`> }` : Str;
type T8 = Eliminate<'aabbcccdde'>;
// type T8 = "abcde"
十进制转二进制
结合数组来实现除法和取模运算来实现进制的转换:
type BuildArray<
Length extends number,
Ele = unknown,
Arr extends unknown[] = []
> = Arr['length'] extends Length
? Arr
: BuildArray<Length, Ele, [...Arr, Ele]>;
type Subtract<Num1 extends number, Num2 extends number> =
BuildArray<Num1> extends [...arr1: BuildArray<Num2>, ...arr2: infer Rest]
? Rest['length']
: never;
type Divide<
Num1 extends number,
Num2 extends number,
CountArr extends unknown[] = []
> = Num1 extends 0 ? CountArr['length']
: GreaterThan<Num2, Num1> extends true ? CountArr['length'] : Divide<Subtract<Num1, Num2>, Num2, [unknown, ...CountArr]>;
type GreaterThan<
Num1 extends number,
Num2 extends number,
CountArr extends unknown[] = []
> = Num1 extends Num2
? false
: CountArr['length'] extends Num2
? true
: CountArr['length'] extends Num1
? false
: GreaterThan<Num1, Num2, [...CountArr, unknown]>;
type Mod<Num1 extends number,
Num2 extends number> = Num2 extends 0 ? never : Num1 extends Num2 ? 0 : GreaterThan<Num1, Num2> extends false ? Num1 : Mod<Subtract<Num1, Num2>, Num2>;
type Join<
List extends Array<string | number>,
Delimiter extends string = ''
> = List extends [string | number, ...infer Rest]
? // @ts-expect-error
`${List[0]}${Delimiter}${Join<Rest, Delimiter>}`
: '';
type ToBinary<Num extends number, TArr extends unknown[] = []> = Divide<Num, 2> extends 0 ? [Mod<Num, 2>, ...TArr] : ToBinary<Divide<Num, 2>, [Mod<Num, 2>, ...TArr]>;
type T9 = Join<ToBinary<10>>;
// type T9 = '1010'