TS的模板字符串

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'
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Raccom

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值