TypeScript工具类型探秘:解锁类型体操新境界

TypeScript在最近一直饱受争议,但是在我看来,作为规范、约束JS的超集,他的优点还是很多的,在多人协调项目上用TS,在规范类型,语法提示上实在不要太友好!所以接下来我为大家介绍它本身的内置工具类型,提升我们类型定义的速度与可读性。

Partial<T>

  • 作用:将类型T的所有属性设置为可选。
  • 例子
typescript 复制代码
interface User {  
  id: number;  
  name: string;  
  email: string;  
}  
 
const updateUser: Partial<User> = {}

在这个例子中,我们创建了一个updateUser对象,它只包含了User接口中的name属性,而idemail属性则是可选的。

在实战中,我们对于一些模型参数的要求是必要的,一些变量的定义却要求不是必要的,就可以使用它(不同业务上,数据类型相同)

  • 源码解析:查看内置源码
ini 复制代码
type Partial<T> = {
    [P in keyof T]?: T[P];
};
  1. 使用映射类型,我们可以为类型 T 中的每个属性 P 定义一个新的类型。keyof T 获取 T 中所有属性的名称组成的联合类型。
  2. 通过 ?,我们告诉 TypeScript 将 T 中属性 P 变为可选的。这意味着无论 T 中的属性 P 是否原本是可选的,在 Partial<T> 类型中,它都将是可选的。
  3. 这里,我们使用 T[P] 来表示属性 P 的原始类型。因此,我们不仅仅添加了可选性,还保留了属性的原始类型。

Required<T>

  • 作用:与Partial相反,Required工具类型将类型T的所有属性变为必填的。
  • 例子
ini 复制代码
interface User {  
  id?: number;  
  name?: string;  
  email?: string;  
}  

type RequiredPerson = Required<User>
 
const updateUser: RequiredPerson = {}

在上面的例子中,User 类型中的 nameid 等属性都是可选的。但是,当我们使用 Required<User> 得到 RequiredPerson 类型时,这几个属性都变为了必需的。

  • 源码解析:查看内置源码
ini 复制代码
type Required<T> = {
    [P in keyof T]-?: T[P];
};
  1. 定义了一个泛型类型 Required,它接受一个类型参数 T
  2. 使用映射类型,我们可以为类型 T 中的每个属性 P 定义一个新的类型。keyof T 会获取 T 中所有属性的名称组成的联合类型。
  3. 通过 -?,我们告诉 TypeScript 移除 T 中属性 P 的可选性。这意味着如果 T 中的属性 P 是可选的,那么在 Required<T> 类型中,它将是必需的。
  4. 这里,我们使用 T[P] 来表示属性 P 的原始类型。因此,我们不仅移除了可选性,还保留了属性的原始类型。

Readonly<T>

  • 作用:将类型T的所有属性变为只读属性,这意味着这些属性不能被重新赋值。
  • 例子
ini 复制代码
type Person = {  
  name: string;  
  age: number;  
};  

type ReadonlyPerson = Readonly<Person>;  

const person: ReadonlyPerson = {  
  name: "Alice",  
  age: 30  
};  

// 下面的代码会报错,因为 name 属性是只读的  
// person.name = "Bob";

在上面的示例中,person 是一个 ReadonlyPerson 类型的对象,其 name 属性是只读的。因此,尝试修改 person.name 的值会导致 TypeScript 编译器报错。

在实战中,我们某些特定的属性不允许被修改,例如一些影响着全局的常量。

  • 源码解析:查看内置源码
ini 复制代码
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
  1. 定义了一个泛型类型 Readonly,它接受一个类型参数 T
  2. 这里使用了映射类型的语法,它遍历类型 T 的所有属性键,并为每个属性键 P 定义一个只读属性。in keyof T 表示我们正在遍历 T 的所有键。
  3. 对于 T 中的每个属性键 P,我们使用 T[P] 来获取 T 类型中对应属性的类型。通过前缀 readonly,我们告诉 TypeScript 该属性是只读的,即不能被重新赋值。

Pick<T, K>

  • 作用:从类型T中选择一些属性K来形成新的类型。这允许你从一个更大的类型中"挑选"出你需要的属性。
  • 例子
ini 复制代码
interface Todo {  
  id: number;  
  title: string;  
  completed: boolean;  
  description: string;  
}  
 
type TodoPreview = Pick<Todo, 'id' | 'title'>;  
 
const todoPreview: TodoPreview = {  
  id: 1,  
  title: 'Buy milk'  
};

在这个例子中,我们只从Todo接口中选择了idtitle属性来创建一个新的TodoPreview类型,用于显示预览信息。

这个方法就很实用,很多时候我们字段相同的情况下都是用联合类型重复定义处理的,这个方法完全可以抽离类型复用。

  • 源码解析:查看内置源码
scala 复制代码
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
  1. 定义了一个泛型类型 Pick,它接受两个类型参数:TKK 是一个扩展自 keyof T 的类型,意味着 K 必须是 T 的属性键的集合。
  2. 使用映射类型,我们为类型 K 中的每个属性 P 定义了一个新的类型。in K 表示我们正在遍历 K 中的每个属性。
  3. 对于 K 中的每个属性 P,我们使用 T[P] 来获取 T 类型中对应属性的类型。这确保了新创建的类型会保留原类型 T 中对应属性的类型。

Exclude<T, U>

  • 作用:从类型T中排除那些可以赋值给类型U的类型。
  • 例子
ini 复制代码
type Animals = 'Dog' | 'Cat' | 'Fish'; 
type LandAnimals = Exclude<Animals, 'Fish'>; // 'Dog' | 'Cat'  

在这个例子中,我们使用ExcludeAnimals类型中排除特定的值。

这个方法用法比较广,例如,一个联合枚举中,我们只需要特定的某几个状态,就可以排除不需要的值

  • 源码解析:查看内置源码
r 复制代码
type Exclude<T, U> = T extends U ? never : T;
  1. 定义了一个泛型类型 Exclude,它接受两个类型参数:TU
  2. 条件类型
    • 如果 TU 的子类型(T extends Utrue),则结果是 never 类型。never 是 TypeScript 中的一个特殊类型,表示一个永远不会有值的类型。在类型操作中,never 类型可以用作一个"过滤器",因为它不会出现在任何有效的值的类型中。
    • 如果 T 不是 U 的子类型(T extends Ufalse),则结果是 T 本身。
  3. 由于条件类型的作用,Exclude<T, U> 将返回类型 T 中所有不能赋值给类型 U 的子类型。换句话说,它排除了 T 中所有与 U 重叠的部分。

Extract<T, U>

  • 作用:类型T中提取那些可以赋值给类型U的类型
  • 例子
ini 复制代码
type Animals = 'Dog' | 'Cat' | 'Fish';  
// type LandAnimals = Exclude<Animals, 'Fish'>; // 'Dog' | 'Cat'  
type AquaticAnimals = Extract<Animals, 'Fish' | 'Crocodile'>; // 'Fish'

在这个例子中,我们使用ExtractAnimals类型中提取特定的值。

这个实战中用的比较少,一般用作获取两个枚举间的相同属性值

  • 源码解析:查看内置源码
r 复制代码
type Extract<T, U> = T extends U ? T : never;

跟Exclude相反,就不多做解释。

Omit<T, K>

  • 作用: 从类型T中排除一些属性K来形成新的类型。这可以用来创建一个不包含某些属性的新类型。
  • 例子
ini 复制代码
type Person = {  
  id: number;  
  name: string;  
  age: number;  
  address: string;  
};  
 
type PersonWithoutAddress = Omit<Person, 'address'>;  
 
const person: PersonWithoutAddress = {  
  id: 1,  
  name: 'John Doe',  
  age: 30,
  number: 10
};

这里,我们创建了一个PersonWithoutAddress类型,它排除了Person类型中的address属性并重写了address类型为number。

实战中也比较常用到,一般都是联合重写或者剔除某个属性。

  • 源码解析:查看内置源码
scala 复制代码
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

比较复杂,联合了两种内置方法,接下来一步步解析

  1. 定义了一个泛型类型 Omit,它接受两个类型参数:TKK 被定义为扩展自 keyof any 的类型,这基本上意味着 K 可以是任何类型的属性键的集合。但是,在实际使用中,K 通常会是 keyof T 的子集,即 T 的某些属性键的集合。
  2. 在这里,我们使用 Exclude<keyof T, K> 来获取 T 中除了 K 以外的所有属性键。
  3. 之前我们解析过 Pick 类型,它用于从类型 T 中选取一部分属性来创建一个新的类型。在这里,我们结合 Exclude 的结果,使用 Pick 来选取 T 中除了 K 以外的所有属性。

不理解没关系,多看几遍就行了

Record<K, T>

  • 作用:用一组类型为K的keys来构造一个对象类型,其值的类型为T。
  • 例子
ini 复制代码
type FeatureFlags = Record<string, boolean>;  
 
const flags: FeatureFlags = {  
  isFeatureAEnabled: true,  
  isFeatureBEnabled: false  
};

这里,我们定义了一个FeatureFlags类型,它允许我们以字符串为键,布尔值为值来创建对象。

实际业务上,我们一般用来列举不确定性的Object,省了key-string:value-统一类型的定义。

  • 源码解析:查看内置源码
scala 复制代码
type Record<K extends keyof any, T> = {
    [P in K]: T;
};
  1. 这里定义了一个泛型类型 Record,它接受两个类型参数:KTK 必须是某个对象类型的键的子集,这是通过 extends keyof any 来约束的。keyof any 表示任何对象类型的键的集合,因此 K 可以是任何对象类型的键的任意子集。
  2. 同样地,这里使用了映射类型的语法,它遍历类型 K 的所有属性键,并为每个属性键 P 定义一个类型为 T 的属性。
  3. 对于 K 中的每个属性键 P,我们定义了一个类型为 T 的属性。这样,我们就构造了一个对象类型,该对象的键是 K 的子集,并且所有键的值都是 T 类型。

ReturnType<T>

  • 作用:获取函数类型T的返回类型。
  • 例子
javascript 复制代码
function handler() {
  return {
    get() {},
    set() {},
    remove() {},
    delete() {}
  }
}

export type Handler = ReturnType<typeof handler>

在这个例子中,我们定义了一个函数 handler,它返回一个对象里包含的几个方法。然后,我们使用 ReturnTypetypeof 操作符来获取这个函数的返回类型,并将结果存储在 Handler 类型中。结果是一个类型,表示 handler 的返回类型。

这个实战中也是比较常用,一般用作描述一个工具函数的返回类型。

  • 源码解析:查看内置源码

注意:用到了三元条件类型判断

  1. 定义了一个泛型类型 ReturnType,它接受一个类型参数 TT 必须是一个函数类型,该函数接受任意数量和类型的参数,并返回任意类型的结果。
  2. 条件类型允许我们根据条件选择两种类型之一,在源码中,我们检查 T 是否扩展(即兼容)于一个函数类型,该函数接受任意参数并返回一个可以推断的类型 R
    • 如果 T 是一个这样的函数类型,那么 infer R 会推断出函数的返回类型,并且条件类型的结果就是 R
    • 如果 T 不是这样的函数类型(或者不能推断出返回类型),那么条件类型的结果就是 any
  3. infer 关键字用于在条件类型中创建一个新的类型变量。在这里,R 是推断出的函数返回类型。TypeScript 会尝试从 T 中推断出 R 的具体类型。

在日常业务使用TS的时候,灵活使用其内置的类型方法,会极大的提高我们的开发效率。

相关推荐
Passion不晚3 小时前
Vue vs React vs Angular 的对比和选择
vue.js·react.js·前端框架·angular.js
甜兒.5 小时前
鸿蒙小技巧
前端·华为·typescript·harmonyos
Jiaberrr8 小时前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
LvManBa9 小时前
Vue学习记录之六(组件实战及BEM框架了解)
vue.js·学习·rust
200不是二百9 小时前
Vuex详解
前端·javascript·vue.js
LvManBa9 小时前
Vue学习记录之三(ref全家桶)
javascript·vue.js·学习
深情废杨杨9 小时前
前端vue-父传子
前端·javascript·vue.js
工业互联网专业10 小时前
毕业设计选题:基于springboot+vue+uniapp的驾校报名小程序
vue.js·spring boot·小程序·uni-app·毕业设计·源码·课程设计
J不A秃V头A10 小时前
Vue3:编写一个插件(进阶)
前端·vue.js
光影少年11 小时前
usemeno和usecallback区别及使用场景
react.js