TypeScript 奇淫巧技

199 阅读1分钟

泛型条件

// 不限制T一定是U的子类型,如果是U子类型,则将T定义为X类型,否则定义为Y类型
T extends U? X: Y
// X换成T,此时返回的 T,满足原来的T中包含U的部分,可以理解为T和U的交集
T extends U? T: never

// never 是任何类型的子类型,也就是说 never 可以赋值给任何类型
// example:
// const value = 123 as never
// const data: string = value

枚举转联合类型

enum StringEnum {
    Small = 'S',
    Medium = 'M',
    Large = 'L',
}
// 👇️ type ValuesUnion = "S" | "M" | "L"
type ValuesUnion = `${StringEnum}`;
// 👇️ type KeysUnion = "Small" | "Medium" | "Large"
type KeysUnion = keyof typeof StringEnum;

对象属性操作

type TestBookType = { width: number; height: number; text?: string; color: 'red' | 'blue' };

// 部分可选
type PartialOption<T, U extends keyof T> = Partial<Pick<T, U>> & Omit<T, U>;
// example
const book_1: PartialOption<TestBookType, 'color'> = { width: 50, height: 100 };
// 用于属性替换
const newBook: PartialOption<TestBookType, 'color'> & { textColor?: 'red' | 'blue' } = book_1;
newBook['textColor'] = newBook['color'];
delete newBook['color'];

// 部分必选
type PartialRequired<T, U extends keyof T> = Required<Pick<T, U>> & Omit<T, U>;
// example
const book_2: PartialRequired<TestBookType, 'text'> = {
    width: 50,
    height: 100,
    text: 'book',
    color: 'blue',
};

// 部分修改,in 操作符会遍历迭代联合类型
type PartialTransform<T, U, N> = { [P in keyof T]: T[P] extends U ? N : T[P] };
// example
const book_3: PartialTransform<TestBookType, 'red' | 'blue', 'white'> = {
    width: 50,
    height: 100,
    text: 'book',
    color: 'white', // 'red' | 'blue' => 'white'
};

类型推断

数组

// infer获取U类型中某个部分的类型
// infer只能在条件类型的extends子句中使用
// infer得到的类型只能在true语句中使用

// 数组元素
type InferArray<T> = T extends (infer U)[] ? U : never;
// 数组移除最后一个元素
type Pop<T extends any[]> = T extends [...infer L, infer R] ? [...L] : [];
// 数组移除第一个元素
type Shift<T extends any[]> = T extends [infer L, ...infer R] ? [...R] : [];
// example:
type ArrayTest = Shift<['ddd', 123]>;
const test: ArrayTest = [123];

对象属性名

type InferRecordKey<T> = T extends Record<infer R, unknown> ? R : never;
// example
type testRecordType = Record<'a' | 'b' | 'c', number>;
type RecordKeyType = InferRecordKey<testRecordType>;
const recordKey: ObjectTest = 'a'; // 'a' | 'b' | 'c';

函数返回值

type InferReturnType<T extends Function> = T extends (...args: any) => infer R ? R : never;
// example
type testFunctionType = () => number;
type testReturnType = InferReturnType<testFunctionType>;
const testReturn: testReturnType = 1;

// 上面@typescript-eslint插件会报错:Don't use `Function` as a type. The `Function` type accepts any function-like value...
// 大体意思是不要用`Function`作为类型参数,因为它接受任何类似函数的值(返回类型未知,可能不安全),调用该函数时无法提供类型安全检查
type InferSafeReturnType<T> = T extends (...args: any) => infer R ? R : never;
// example
type testReturnType = InferSafeReturnType<testFunctionType>;
const testSafeReturn: testReturnType = 1;

字符串

// 字符串首字母
type InferStringFirst<T extends string> = T extends `${infer First}${infer _}` ? First : never;
// 字符串第二个字母
type InferStringSecond<T extends string> = T extends `${infer First}${inter Second}${infer _}` ? Second : never;
// example
type testStringFirstType = InferStringFirst<'abc'>
const stringFirst: testStringFirstType = 'a'
type InferStringSecondType = InferStringSecond<'abc'>
const stringFirst: testStringFirstType = 'b'

// 字符串左边无空格
type TrimLeft<S extends string> = S extends `${infer L}${infer R}`
    ? L extends ' ' | '\n' | '\t'
        ? TrimLeft<R>
        : S
    : '';
// example:
type ArrayTest = TrimLeft<' test'>;
const test: ArrayTest = 'test';

联合类型限制分配

type Normal<A, B> = A extends B ? A : false;
// [ ] 会限制联合类型分配
type Strict<A, B> = [A] extends [B] ? A : false;

type a = Normal<1 | 2 | 3, 1>; // false | 1
type a1 = Strict<1 | 2 | 3, 1>; // false

type RefObj = { value: number };

type b = Normal<{ value: number } | { name: string }, RefObj>; // false | { value: 12; }
type b1 = Strict<{ value: number } | { name: string }, RefObj>; // false

// 传入的对象必须满足内部有一个 value: number
type b2 = Strict<
    { value: number } | { test: string; value: number } | { name: string; value: number },
    RefObj
>;