html拼接字符串-尝试使用 TS 模板字符串类型

TS字符模板类型的写法和JS模板字符串非常相似。 我们看一下官方的例子:

type World = 'world';
type Greeting = `hello ${World}`;
// type Greeting = "hello world"

除了上面的类型与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"

联合类型会被一一组合成模板,最终生成联合类型。 当然,这只是模板字符串函数的冰山一角,接下来的内容将会更加精彩。

结合外部字符串工具类型 、 、 和 ,我们可以进一步玩出更多花样:

type World = 'world';
type Greeting = `HELLO ${Uppercase}`;
// 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 }`]: () => T[K];
};
// 增加 setter 方法,参数类型就是对应值的类型
type AddSetter<T extends object> = {
	[K in keyof T & string as `set${ Capitalize }`]: (arg: T[K]) => void;
};
type Student = {
	name: string;
	age: number;
	gender: 'MALE' | 'FEMAL';
};
type AddGetterAndSetter<T extends object> = T & AddGetter & AddSetter;
type T1 = AddGetterAndSetter<Student>;

这样T1就包含了Student中的属性,也包含了属性的get、set方法html拼接字符串,变量类型也一一对应。

组合推断以打开字符串魔术

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 }${ First }` : Str;
type T3 = ReverseString<'abcdef'>;
// type T3 = "fedcba"

在前面的类型推断中,通过将模板字符串与infer结合,取出第一个字母First和字符串剩余部分Rest,通过递归调用不断翻转字符串的剩余部分html拼接字符串,直到难以完成为止模式匹配并退出。

实现字符串查找替换类型:

type ReplaceAll<Str extends string, Sub extends string, NSub extends string> = Str extends `${ infer Before }${ Sub }${ infer Rest }` ? `${ Before }${ NSub }${ ReplaceAll }` : 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> = 

使用链表的长度,我们甚至可以估计字符串的宽度:

字符串宽度估计

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 }${ 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 }` : 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}`
  : '';
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'

以上就是本文的全部内容。 希望这个神奇的演示能给你带来启发。