Saber2pr's Blog

Typescript类型运算

首先有这么一个类型

type Test = {
  a: string
  b: number
}

怎么给它增加一个属性呢?

在解决这个问题之前,先了解一下 typescript 的基础操作:

keyof

获取一个类型的所有属性集合(union)

type keys = keyof Test // "a" | "b

keyof 运算符用来获取一个类型的所有属性集合

| 运算符

扩增 union

type result = keys | "c" // "a" | "b" | "c"

这个很简单。

mapped type

遍历 union

type result = { [P in keys]: Test[P] }

这段代码相当于如下伪代码

var result = keys.map(P => Test[P])

就是把 Test 类型映射为另外一种类型

extends

条件语句

type isExtendsA = keys extends "a" ? "yes" : "no" // "no"
type isExtendsAB = keys extends "a" | "b" ? "yes" : "no" // "yes"

extends 在 typescript 里被称作条件语句,用来判断一个类型是否是指定类型的子集


有以上基础就可以实现我们的需求了:

比如给 Test 类型增加一个属性 c,类型是 Function 类型

首先先获取它的 union

type Test = {
  a: string
  b: number
}

type keys = keyof Test // "a" | "b"

给 keys 增加一个"c"

type keys_added_c = keys | "c" // "a" | "b" | "c"

然后遍历这个新的 union

type result = { [P in keys_added_c]: any }
// {a: any;b: any;c: any;}

成功增加了一个属性 c,但是所有属性的类型都丢失了。

所以应该用条件语句判断 P,如果是 Test 原本有的属性,就保留原来的类型,否则指定新类型比如 Function

type result = { [P in keys_added_c]: P extends keys ? Test[P] : Function }
// {a: string;b: number;c: Function;}

把上述过程封装一下就是:

type Test = {
  a: string
  b: number
}
/**
 * get the keys which extends Filter
 */
export type GetKeys<T, Filter = any> = {
  [P in keyof T]: T[P] extends Filter ? P : never
}[keyof T]
/**
 * add a key to keys
 */
export type AddKey<T, K> = GetKeys<T> | K
/**
 * add a key to type
 */
export type Add<Target, K extends string | number | symbol, T = any> = {
  [P in AddKey<Target, K>]: P extends keyof Target ? Target[P] : T
}

type result = Add<Test, "c", Function> // {a: string;b: number;c: Function;}

关于 Typescript 还有很多有趣的特性,有时间再更新这篇博客。

这里有一些常用的 Ts 工具类型:

@saber2pr/ts-lib