目录
- 前言
- Utility Types 是什么?
- 常用 Utility Types
- 前置知识
- `typeof`
- `keyof`
- `typeof` 和 `keyof` 的区别
- `never` 关键字
- `extends` 关键字结合条件判断
- `infer` 类型推断(模式匹配)
- 判断是与非
- 判断两个类型是否相等或兼容
- 循环
- 递归嵌套
- 字符串数组
- 协变(Covariance)
- 逆变(Contravariance)
前言
TypeScript 体操(TypeScript Gymnastics)是指在 TypeScript 中进行各种类型操作和转换的技巧。这些技巧可以帮助我们更好地利用 TypeScript 的类型系统,提高代码的安全性和可维护性。Utility Types 是 TypeScript 2.8 版本引入的,它们提供了一种方式来转换和操作现有的类型。
正文开始
,如果觉得文章对您有帮助,请帮我三连+订阅,谢谢
💖💖💖
Utility Types 是什么?
Utility Types 是一种特殊的类型,它们不是具体的值类型,而是可以对现有类型进行操作的类型。这些操作包括但不限于:选择属性、排除属性、从属性中提取类型等。
常用 Utility Types
TypeScript 提供了许多内置的 Utility Types,以下是一些常用的 Utility Types:
Partial<T>
:将类型T
的所有属性设置为可选。Readonly<T>
:使类型T
的所有属性变为只读。Pick<T, K>
:从类型T
中选取部分属性,创建一个新的类型。Omit<T, K>
:从类型T
中排除部分属性,创建一个新的类型。Record<K, T>
:创建一个类型,其键为K
类型,值为T
类型的字典。Exclude<T, U>
:从类型T
中排除类型U
的所有属性。Extract<T, U>
:从类型T
中提取出类型U
的所有属性。
前置知识
typeof
typeof
是一个 JavaScript 操作符,用于获取一个变量或属性的类型。在 TypeScript 中,typeof
也用于获取一个位置的类型,但它通常与类型守卫一起使用来区分不同的情况。
-
基本用法:
let myVar: string = "Hello, World!"; console.log(typeof myVar); // "string"let obj = {name: "张三",age: 20, }; type ObjType = typeof obj; // { name: string; age: number; }
-
与类型守卫一起使用:
function printId(something: any) {switch (typeof something) {case "string":console.log(`String: ${something}`);break;case "number":console.log(`Number: ${something}`);break;default:console.log(`Unknown`);} }
keyof
keyof
是 TypeScript 中的一个关键字,用于从类型中提取所有公共属性的键,这些键可以是字符串字面量或者数字或符号,取决于属性的类型。
-
基本用法:
type Point = {x: number;y: number; };type PointKeys = keyof Point; // "x" | "y"
-
用于索引访问操作:
let point: Point = { x: 1, y: 2 }; let key: keyof Point = "x"; // "x" | "y" console.log(point[key]); // 1
typeof
和 keyof
的区别
-
用途:
typeof
通常用于在运行时获取变量的类型。keyof
用于在编译时从类型中提取键。
-
类型:
typeof
的结果是一个类型,如"string"
、"number"
、"boolean"
等。keyof
的结果是联合类型,表示一个类型的所有键。
never
关键字
- 两个不相交的基本类型进行相交操作为
never
never
和任何类型相交操作,返回never
- 两个相同的基本类型进行联合操作为其本身
type Str1 = "a" & "c"; // Str1 = never
type Str2 = "a" & never; // Str2 = never
type Str3 = "a" | "a"; // Str3 = "a"
extends
关键字结合条件判断
这个关键字有两个功能:1. 继承属性,2. 条件判断。
条件类型,如同代码中的
if ... else
或三目运算
type A = string;
type B = number;
type Example = A extends B ? true : false; // false
infer
类型推断(模式匹配)
模式匹配是 TypeScript 最有用的特性之一,许多复杂类型操作都基于它。
type A = [1, 2, 3];
type ExampleA = A extends [infer First, ...infer Rest] ? First : never; // 1
type B = "123";
type ExampleB = B extends `${infer FirstChar}${infer Rest}` ? FirstChar : never; // '1'type InferArray<T> = T extends (infer C)[] ? C : never;
const item: InferArray<number[]> = 1; // number
判断是与非
// 与,即 C1,C2 同为真
type And<C1 extends boolean, C2 extends boolean> = C1 extends true? (C2 extends true ? true : false): false;// 或,即 C1,C2 有一个为真
type Or<C1 extends boolean, C2 extends boolean> = C1 extends true? true: C2 extends true? true: false;// 非,即反转 C 的真假状态
type Not<C extends boolean> = C extends true ? false : true;
判断两个类型是否相等或兼容
type CheckLeftIsExtendsRight<T, R> = T extends R ? true : false;
type Example1 = { a: 1; b: 2 } extends { a: 1 } ? true : false; // true
type Example2 = 1 | 2 extends 1 ? true : false; // false
循环
extends
分布式条件类型,当泛型参数 T 为联合类型时,条件类型即为分布式条件类型,会将 T 中的每一项分别分发给extends
进行比对。
in
映射类型,固定写法,in
操作符会分发 T 成为新对象类型的键。
extends
和in
都可以遍历键。extends
加never
能过滤一些不想要的值。
type Example1<T> = T extends number ? T : never;
type Result1 = Example1<"1" | "2" | 3 | 4>; // 3 | 4// 映射类型,固定写法,in 操作符会分发 T 成为新对象类型的键
interface Person {name: string;age: number;sex: boolean;
}
type Example2<T> = {[Key in keyof T]: T[Key];
};
type Result = Example2<Person>; // { name: string; age: number; sex: boolean;}type BaseType = string | number;
type Example3<T> = {[Key in keyof T as T[Key] extends BaseType ? Key : never]: T[Key];
};
type Result2 = Example3<Person>; // { name: string; age: number; }
递归嵌套
type Example<C extends boolean = true, Tuple extends unknown[] = [1]> = C extends true? Example<false, [...Tuple, 1]>: Tuple;type Result = Example; // [1, 1]
字符串数组
type NumberLike = number | `${number}`;
let a: NumberLike = '6';
协变(Covariance)
协变是指如果类型 A
是类型 B
的子类型,那么 T<A>
也是 T<B>
的子类型。换句话说,类型可以在其子类型之间进行转换。
例子:常规赋值
class Animal {name = 'null';
}class Dog extends Animal {breed = '金毛';
}let animals: Animal = new Animal();
let dogs: Dog = new Dog();
dogs.name = '旺财';animals = dogs; // 协变,子类型可以赋值给父类型
// dogs = animals; // error : animals 中缺少 breed 字段console.log(animals.name); // 旺财
在这个例子中,Dog
是 Animal
的子类型,我们可以将 Dog
赋值给 Animal
,并打印 name
。
逆变(Contravariance)
逆变是指如果类型 A
是类型 B
的子类型,那么 T<B>
是 T<A>
的子类型。换句话说,父类型参数可以赋值给子类型参数。
例子:函数参数
class Animal {name = 'null';
}class Dogextends Animal {breed = '金毛';
}let treatAnimal = (animal: Animal) => {console.log(`Treating animal: ${animal.name}`);
};let treatDog = (dog: Dog) => {console.log(`Treating dog: ${dog.breed}`);
};treatDog = treatAnimal; // 逆变,父类型参数可以赋值给子类型参数
// treatAnimal = treatDog; // Error: 不能将子类型参数赋值给父类型参数const myDog = new Dog();
myDog.name = "BB";
myDog.breed = "边牧";treatDog(myDog);
在这个例子中,treatDog
接受 Dog
类型参数,而 treatAnimal
接受 Animal
类型参数。由于 Dog
是 Animal
的子类型,所以 treatDog
可以赋值给 treatAnimal
。
这篇文章介绍了 TypeScript 中一些常见的类型操作技巧。通过掌握这些技巧,你可以更好地利用 TypeScript 的类型系统,提高代码的安全性和可维护性。希望这篇文章对你有所帮助!