TypeScript自带的工具泛型

前言

前面总结了ts的高级类型,下面再来说说ts给我们提供的一些基于这些高级类型而成的工具泛型。

Partial

/**
 * Make all properties in T optional
 * 将T中的所有属性设置为可选
 */
type Partial<T> = {
    [P in keyof T]?: T[P];
};

官方提供的这个方法,就是将T中的所有属性设置为可选类型。可以看到它使用了inkeyof T来遍历T中的所有key(此时P就代表key),可以看到它使用了?:来将类型改为了可空类型,最后再通过T[P]取值的形式,拿到key对应的属性,最后就生成了一个所有key都和T一样,但都是可空类型的新T类型。下面我们看看效果:

image.png

可以看到此处的类型已经发生变化。

Required

/**
 * Make all properties in T required
 * 使T中的所有属性都是必需的
 */
type Required<T> = {
    [P in keyof T]-?: T[P];
};

RequiredPartial是相反的,它的作用是使T中的所有属性都是必需的[P in keyof T]-?: T[P]里面的-?可以看做是取消掉?的意思,直接贴效果:

image.png

可以看到,所有可选类型都变成必选了。

Readonly

/**
 * Make all properties in T readonly
 * 将T中的所有属性设置为只读
 */
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

顾名思义,Readonly的作用就是将T中的所有元素设置为只读类型。

image.png

我们可以看的类型agename已经变成只读类型了。

Pick

/**
 * From T, pick a set of properties whose keys are in the union K
 * 从T中,选择一组键位于联合K中的属性
 */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

根据 K extends keyof T 可以得知 K 是单个类型都位于 T 中的联合类型,首先通过 [P in K] 取出联合类型 K 中所有的类型,再将其和 T[P] 的值关联起来,最后返回了一个新的 interface 类型,这个类型里面只包含了联合类型 K 的所有类型。

interface Person {
    name: string,
    age: number,
    sex: string
}

type nameAndAge = "name" | "age"

const nameAndAgePerson: Pick<Person, nameAndAge> = {
    name: "zl",
    age: 27
}

如上示例,因为 K(nameAndAge) 的类型为 name|age ,所以通过 Pick<Person,nameAndAge> 得到的类型是 { name:string , age:number }

Record

/**
 * Construct a type with a set of properties K of type T
 * 将联合类型K的所有值作为Key,T作为类型,生成一个新的类型
 */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

同样的根据 K extends keyof any 我们可以知道 K 是个联合类型,通过 [P in K] 将联合类型的所有值都取了出来,再将其和 T 关联起来,最后返回了一个新的 interface 类型,这个类型里面的所有属性的类型都是 T 类型。

interface Person {
    name: string,
    age: number,
    sex: string
}

type TeacherAndWorker = "teacher" | "worker"

const person: Record<TeacherAndWorker, Person> = {
    teacher: { name: 'zl', age: 27, sex: "M" },
    worker: { name: 'zl', age: 27, sex: "M" }
}

如上示例可以看出,Record 的作用就是将 K(TeacherAndWorker) 的所有子类型作为 新interface 子属性的 key ,将 T(Person) 作为 新interface 所有子属性的值。

Exclude

/**
 * Exclude from T those types that are assignable to U
 * 从T中排除可分配给U的类型
 */
type Exclude<T, U> = T extends U ? never : T;

Exclude 最常用的还是结合两个联合类型来使用的,我们能通过 Exclude 取出 T 联合类型在 U 联合类型中没有的子类型。

interface Person {
    name: string,
    age: number,
    sex: string
}

interface Alien {
    name: string,
    age: number
}

const personKeysExcludeAlienKeys: Exclude<keyof Person, keyof Alien> = "sex"

如上所示,T(keyof Person = name|age|sex) 联合类型在 U(keyof Alien = name|age) 联合类型中没有的子类型只有 sex,所以通过 Exclude<keyof Person, keyof Alien> 返回的类型就是 sex。下面我们再来看看 interface 之间的 Exclude

const alien: Exclude<Alien, Person> = {
    name: "zl",
    age: 27
}    

可以看到,通过 Exclude<Alien, Person> 我们得到的类型就是 Alien 类型,因为 T(Alien) extends U(Person)false ,所以返回 T(Alien)。这种方式我是想不到什么使用场景。

Extract

/**
 * Extract from T those types that are assignable to U
 * 从T中提取可分配给U的类型
 */
type Extract<T, U> = T extends U ? T : never;

ExtractExclude 是相反的,最常用的还是结合两个联合类型来使用的,我们能通过 Extract 取出 T 联合类型在 U 联合类型中所有重复的子类型。

image.png

如图所示,此时通过 Extract<keyof Person, keyof Alien> 返回的是联合类型 name|age

Omit

/**
 * Construct a type with the properties of T except for those in type K.
 * 构造一个属性为T的类型,但类型K中的属性除外。
 */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Omit 就是 PickExclude 的组合,首先我们可以通过 K extends keyof any 得知 K 为联合类型,然后通过 Exclude<keyof T, K> 我们取出了在联合类型 keyof T 中有,而在 K 中没有的子属性(这里我们假设是 联合类型M ),最后再通过 Pick<T,M> 得到一个新的 interface 类型,这个类型里面只包含了联合类型 M 的所有类型。简单的讲 Omit 就是从类型 T 中取出在 联合类型K 中所没有的类型。

interface Person {
    name: string,
    age: number,
    sex: string
}

const person: Omit<Person, "name" | "age"> = { sex: "M" };

如上示例,我们取出了在 T(Person) 里面除去 K("name"|"age") 以外的类型 sex ,并组成了一个新的 interface

NonNullable

/**
 * Exclude null and undefined from T
 *  从T中剔除null和undefined
 */
type NonNullable<T> = T extends null | undefined ? never : T;

就是字面意思,去除掉联合类型中的 nullundefined 类型。如下图所示,

image.png

使用 NonNullable 之后去掉了联合类型 null | undefined | number | string 中的 nullundefined 类型,剩下了 number|string

Parameters

 /**
 * Obtain the parameters of a function type in a tuple
 * 获取元组中函数类型的参数
 */
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

根据 <T extends (...args: any) => any> 我们能够知道 T 是个函数类型,再通过 T extends (...args: infer P) => anyT 的形参列表约束成了泛型 P ,所以通过三元运算返回的是函数类型 T 的形参元组。

function fn(str: string, num: number) {}
type fnParamtersTuple = Parameters<typeof fn>
image.png

如图所示,fnParamtersTuple 的类型为,fn 的形参类型的元组。

ConstructorParameters

/**
 * Obtain the parameters of a constructor function type in a tuple
 * 获取元组中构造函数类型的参数
 */
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;

Parameters 类似,只不过 ConstructorParameters 获取的是构造函数的形参元组。下面以官网上介绍 interface类静态部分与实例部分的区别 中的例子改变一下作为demo。

interface ClockConstructor {
    new(hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
    tick();
}

//通过 ConstructorParameters<ClockConstructor> 获取构造函数中的形参元组
function createClock(ctor: ClockConstructor, ...arg: ConstructorParameters<ClockConstructor>): ClockInterface {
    return new ctor(...arg);
}

class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}

let digital = createClock(DigitalClock, 12, 17);

ReturnType

/**
 * Obtain the return type of a function type
 * 获取函数类型的返回类型
 */
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

Parameters 类似,只不过 ReturnType 获取的是函数类型的返回类型。

 function fn() : string {
     return ""
 }
 type fnReturnType = ReturnType<typeof fn>
image.png

InstanceType

/**
 * Obtain the return type of a constructor function type
 */
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;

Parameters 类似,只不过 ConstructorParameters 获取的是构造函数返回值的类型。

class Person { }

class Alien { }

type p0 = InstanceType<typeof Person> // Person

interface PersonConstructor {
    new(): Person
}

type p1 = InstanceType<PersonConstructor> // Person

interface PersonAndAlienConstructor {
    new(): Person | Alien
}

type p2 = InstanceType<PersonAndAlienConstructor> // Person | Alien
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容