一个故事带你了解TypeScript常见的内置类型

200 阅读6分钟

为了方便 TypeScript 用户,TypeScript 开发团队为我们提供了许多内置实用类型( Utility Types)。

今天我将用一个故事带大家了解几个开发中常用的内置类型。

出生

小明出生了,他的名字就叫小明,虽然很大众,但是父母并不打算为他改名字。

那么我们如何命名一个小明类型,并且限定好他的固定属性呢?

我们可以使用ReadOnly

ReadOnly

使用语法:type readOnlyA = Readonly<A> 作用:得到一个和A类型属性相同但是全部只读的新类型

type Person = {
    name: string
    gender: number
}
type XiaoMing = Readonly<Person>
/* 等价于:
type XiaoMing = {
    readonly name: string;
    readonly gender: number;
} */

这时,通过使用ReadOnly包裹Person类型得到的XiaoMing类型的各个属性都是readonly的,即初始化之后不可更改。

让我们实例化一个小明吧:

let xiaoMing:XiaoMing = {
    name: 'xiaoMing',
    gender: 1
}
// xiaoMing.age = 1 // 语法报错: 无法分配到 "age" ,因为它是只读属性。

礼物

时间一转眼,小明已经四岁,小明爸想给孩子买一些礼物,于是他来到了超市,看到了以下物品:

type Things = {
    辣条: string,
    奥特曼变身器: string,
    学习用品: string
}
const things:Things  = {
    辣条: '123',
    奥特曼变身器: '迪迦神光棒',
    学习用品: '全套三角尺'
}

但是小明爸不知道孩子喜欢什么,超市管理员和小明爸很熟,说可以先全部拿回去,小明不喜欢的再退回来。

于是,我们需要一个变量包含上面类型的属性,但同时每一个属性又都是可选的。此时我们可以使用partial

Partial

使用语法:type PartialA = Partial<A> 将类型定义的所有属性都修改为可选。

type ChoiceThings = Partial<Things> 
/* 等价于:
type ChoiceThings = {
    辣条?: string | undefined;
    奥特曼变身器?: string | undefined;
    学习用品?: string | undefined;
} */

小明见到了礼物,果断的选择了两样:

const myThings:ChoiceThings  = {
    辣条: '123',
    奥特曼变身器: '迪迦神光棒'
} // 由于所有的属性都是可选的,所以我们可以只需要其中几个属性

兴趣班

小明的妈妈想给孩子报兴趣班,于是他给了小明一个课表,让小明自己选择:

type Lesson = {
    english : number //number代表星期几
    dance ?: number
    draw ?: number
}

但是小明全都感兴趣,他想构造一个自己的课表类型,这个类型需要每一个属性。

所以Requied就派上了用场:

Rquied

用法:Readonly<Type> 构造一个类型,该类型由设置为 Type 的所有属性组成。

type MyLesson = Required<Lesson>
/* 等价于:
type MyLesson = {
    english: number;
    dance: number;
    draw: number;
} */

小明使用自己的课表类型初始化了一个课程安排:

const myLesson: MyLesson = {
    english: 1,
    dance: 3,
    draw: 5
}

朋友

在兴趣班里,小明交到了Timor,Lulu, Fizz三个好朋友,他们都是友好的约德尔人。

type Frind = 'timor' | 'lulu' | 'fizz'

现在,小明想做一个朋友簿,记录每一个朋友的信息。但是懒惰的程序员不想再去写一个朋友薄类型了,我们想利用已有的类型来确定。此时我们可以用到Record

Record

使用语法:type A = Record<Key, Type>构造一个对象类型,其属性键为Keys,属性值为Type。此实用程序可用于将一种类型的属性映射到另一种类型。

type Frinds = Record<Frind, Person> // Person类型在文章开头声明过
/* 等价于:
type Frinds = {
    timor: Person;
    lulu: Person;
    fizz: Person;
} */

于是小明就可以利用该类型声明一个朋友薄:

const frinds:Frinds = {
    timor: {
        name: 'timor',
        gender: 1
    },
    lulu: {
        name: 'lulu',
        gender: 0
    },
    fizz: {
        name: 'fizz',
        gender: 1
    }
}

新课表

小明最近时间有些紧张,随着对兴趣课的深入,他需要在下面的全部课程中做出选择:

type LessonDetail = {
    lessonName: string // 课程名
    day: number // 星期几上课
    timeConsumed: number // 课程消耗时间
}
type AllLesson = Record<'english' | 'dance' | 'draw', LessonDetail> // 这里使用到了Record

一番抉择下,小明放弃了成为舞蹈巨星的道路。现在他需要一个新的课程类型,用来制作他的新课表。

Pick

用法: Pick<Type, Keys> 通过从Type中选取一组属性Keys(字符串文字或字符串文字的并集)来构造一个新类型。

type NewLesson = Pick<AllLesson, 'english' | 'draw'>
/* 等价于:
type NewLesson = {
    english: LessonDetail;
    draw: LessonDetail;
} */

Omit

和Pick正好相反,Omit通过从Type中选取所有属性Type然后删除Keys(字符串文字或字符串文字的并集)来构造类型。

用法: Omit<Type, Keys>

所以我们也可以使用Omit来声明新类型:

type NewLesson = Omit<AllLesson, 'dance'>
// 等价于 type NewLesson = Pick<AllLesson, 'english' | 'draw'>

小明使用NewLesson声明了新的课表:

const newLesson: NewLesson = {
    english: {
        lessonName: 'verb',
        day: 1,
        timeConsumed: 2
    },
    draw: {
        lessonName: 'egg',
        day: 5,
        timeConsumed: 3
    }
}

Exclude

回到开始的问题,我们的新课表类型的构造也可以使用Exclude来进行:

用法:Exclude<UnionType, ExcludedMembers>

Exclude通过从联合类型UnionType中排除ExcludedMembers联合类型的所有成员来构造类型。

所以,我们的NewLesson属性也可以这样构造:

type NewLesson = Record<Exclude<'english' | 'dance' | 'draw', 'dance'>, LessonDetail>
// 等价于 Record<Exclude<'english' | 'draw'>, LessonDetail>

当然,这样做有点自己给自己添麻烦了。这里只是为了演示Exclude的作用才这么写。

有趣的函数

这天,Timor拿来了一个函数:

// Timor是这么写的
function stringToSum(a: string, b: string):Reason {
    const res = parseInt(a) + parseInt(b)
    return {
        res,
        isOk: !isNaN(res)
    }
}

小明也想使用一下该函数,所以他使用了Parameters, ReturnType 来定义函数的请求参数返回值的类型。

Parameters

用法:Parameters<typeof fnName> 从函数类型 Type 的参数中使用的类型构造元组类型。

let args: Parameters<typeof stringToSum> // 相当于 let args: [a: string, b: string]

ReturnType

用法:ReturnType<typeof fnName> 构造一个由函数 Type 的返回类型组成的类型。

let res: ReturnType<typeof stringToSum> // 相当于 let res: Reason                   

小明利用了上面声明的变量来使用Timor的函数:

args = ['12', '34']
res = stringToSum(...args)

总结

到这里,我们已经介绍了9种常用的TypeScript程序类型,他们分别是:

  • ReadOnly<Type> 得到一个和Type类型属性相同但是全部只读的新类型
  • Partial<Type> 将Type类型定义的所有属性都修改为可选。
  • Rquied<Type> 构造一个类型,该类型由设置为 Type 的所有属性组成。
  • Record<Key, Type> 构造一个对象类型,其属性键为Keys,属性值为Type。
  • Pick<Type, Keys> 从Type中选取一组属性Keys来构造一个新类型。
  • Omit<Type, Keys> 从Type中选取所有属性Type然后删除Keys来构造类型。
  • Exclude<UnionType, ExcludedMembers> 从联合类型UnionType中排除ExcludedMembers联合类型的所有成员来构造类型。
  • Parameters<typeof fnName> 从函数类型 Type 的参数中使用的类型构造元组类型。
  • ReturnType<typeof fnName> 构造一个由函数 Type 的返回类型组成的类型。

说完了常用程序类型,小明的故事也先就先告一段落啦。以后如果在面试或者职场中,有人向你提起程序类型,你就可以清清嗓子,然后说:”咳咳,那我就要给你讲一个小明的故事了...”

ts系列文章

更实用的TypeScript指南:不墨迹的入门 - 掘金 (juejin.cn)

更实用的TypeScript指南:从ES6的class到TS的装饰器 - 掘金 (juejin.cn)

一个故事带你了解TypeScript程序类型 - 掘金 (juejin.cn)