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 的类型系统,提高代码的安全性和可维护性。希望这篇文章对你有所帮助!

  • 47
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 26
    评论
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

子羽bro

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

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

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

打赏作者

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

抵扣说明:

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

余额充值