TypeScript体操(一):从基础到进阶

前言

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
    

typeofkeyof 的区别

  1. 用途

    • typeof 通常用于在运行时获取变量的类型。
    • keyof 用于在编译时从类型中提取键。
  2. 类型

    • typeof 的结果是一个类型,如 "string""number""boolean" 等。
    • keyof 的结果是联合类型,表示一个类型的所有键。

never 关键字

  1. 两个不相交的基本类型进行相交操作为 never
  2. never 和任何类型相交操作,返回 never
  3. 两个相同的基本类型进行联合操作为其本身
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 成为新对象类型的键。

  1. extendsin 都可以遍历键。
  2. extendsnever 能过滤一些不想要的值。
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); // 旺财

在这个例子中,DogAnimal 的子类型,我们可以将 Dog 赋值给 Animal,并打印 name

逆变(Contravariance)

逆变是指如果类型 A 是类型 B 的子类型,那么 T<B>T<A> 的子类型。换句话说,父类型参数可以赋值给子类型参数。

例子:函数参数

class Animal {
  name = 'null';
}

class Dog

 extends 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 类型参数。由于 DogAnimal 的子类型,所以 treatDog 可以赋值给 treatAnimal


这篇文章介绍了 TypeScript 中一些常见的类型操作技巧。通过掌握这些技巧,你可以更好地利用 TypeScript 的类型系统,提高代码的安全性和可维护性。希望这篇文章对你有所帮助!

评论 26
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

子羽bro

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

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

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

打赏作者

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

抵扣说明:

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

余额充值