前言
很早就听过 TypeScript,当时自己试了下觉得挺简单的,就是给数据声明一个类型提高可读和维护性,不过当时 TS 还不盛行,项目也没机会用到就一直落着,随着 TS 不断普及,许多项目都开始引入了 TS,这不最近在 github 看到一些 TS 代码后,心里开始吐槽:
这好好的非要给 JS 装饰的这么花里胡哨的?我都认不出来这是什么语言了,你确定这是提升 JS 语言可读性而不是添乱?有同学跟我一样的请举个手!
后来静下心来想了想,既来之则安之,与其吐槽,不如静下心来好好重温下 TS ,在这期间也提取了一些有必要掌握的关键字,现在再去读一些 TS 代码心里总算有底了,可以说理解这些关键字的含义,对大部分的 TypeScript 代码都能看得懂。
当然了,光看懂还不行,还得学会运用,但我们现在的目标还是先看懂吧~
提示:本文假设你已经认识一些基本的 TS 语法,如果是第一次接触 TS,建议看下我前面写过的 TypeScript 日常基本语法
正文
1. keyof
keyof
可以获取对象里的所有 key ,跟 JS 中的 Object.keys()
类似,假如我们想要获取某个 interface 接口里的所有 key,就可以用到 keyof
,我们来看下例子:
1
2
3
4
5
6
7
8
9
10
11interface User { name: string; age: number; birthday: string; } // 下面等同于 type keysType = 'name' | 'age' | 'birthday' type keysType = keyof User const key:keysType = 'name'
看起来好像有点鸡助的样子,无非就是复制 key ,别急,它的真正作用在于搭配其它关键字,我们往下看就能知道了。
2. in
2.1 in keyof
in
可以用来配合 keyof
,我们从上面得知 keyof
是获取所有的 key,假如我们想对每个 key 进行额外的处理怎么做呢?
这里就可以用到 in
关键字,我们来看下例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14interface Properties { name: string; age: number; ate: () => void; likeColors: string[]; } // 将 Properties 的 key: valueType 都复制过来了 type CopyProperties = { [K in keyof Properties]: Properties[K] } // 等同于 // type CopyProperties = Properties
由上面例子得知,通过 in keyof
遍历的同时,还能使其它地方能访问到这个 K,有没有开始感受到 keyof
带来的作用了?
2.2 单独用 in
in
单独使用的话跟 JS 中的 in 类似
1
2
3
4
5
6
7
8
9
10
11// 表示 foo 定义的 key 必须包含 a 或 b 或 c type Foo = { [K in 'a' | 'b' | 'c']: number } const obj: Foo = { a: 100, b: 200, c: 300, d: 400 // 报错 }
3. extends
3.1 interface extends
从字面上理解 interface extends
好像就叫接口继承,你可以这么称呼,但我个人更喜欢叫接口合并
,我们来看例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16interface Action { bark: () => void } interface People extends Action{ name: string; age: number; } // 上面的 People 等同于下面 interface Peoople { name: string; age: number; bark: () => void }
3.2 <T extends U>
敲黑板:请忘记之前我们学过的继承概念,因为下来要讲的 extends 根本不叫继承。
从字面上理解 <T extends U > 好像就叫泛型继承?不!这叫泛型约束
,什么是泛型约束?这里我总结一下就是:
泛型传递进来的类型必须满足 U 里的所有属性和类型
我们来看例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32interface U { name: string; age: number; } type Foo<T extends U> = { colors: string[]; sex: boolean; user: T } interface User { name: string; age: number; } // User 满足 U,所以 TS 不会报错 const firstPerson: Foo<User> = { colors: ['Blue'], sex: true, user: { name: 'Jack', age: 20 } } interface Water { color: 'Transparent'; age: 10900000000000 } // Water 这个类型不满足 U,所以 TS 会报错 const secondPerson: Foo<Water> = {}
通过例子可以得出,定义的泛型被限制住了,不能随便传递,我们再来举另外一个例子来加深印象:
1
2
3
4
5
6
7
8
9
10
11
12interface Properties { length: number; } function countStrLength<T extends Properties>(arg: T) { console.log('字符串的长度是' + arg.length) } // 正确,字符串里有 length 属性而且是 number 类型 countStrLength('Hello,world') // 传递的 number 类型没有 length 属性, TS 报错! countStrLength(1000)
注意:泛型约束不仅仅约束 key ,它还限制了 valueType,像上面的 length: number
如果改为 length: string
,TS 也会报错,因为 length 返回的是一个 number
类型而不是 string
4. 索引签名
索引签名
指的是 [K : keyType]: valuetype
, 其中 K 可以随意命名,说的通俗点就是用来定义未知数量的 keyType: valueType
,我们来看下例子就懂了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37interface User { name: string; age: 20; } /* 假设 User 只有 name/age 两个属性,但后续可能会新增其它属性,且数量是未知的, 这种情况下我们就可以用索引签名来代表未知的 keyType: valueType 稍作修改就会变成下面这样 */ interface User { name: string; age: number; [k: string]: any } // 接下来新增的的属性 TS 都不会报错了。 const firstPeople: User = { name: 'Jack', age: 20, sex: 0, colors: ['Blue', 'Yellow'] } // 如果你想让 [k: string]: any 变成可选状态,只需在后面加个问号 `?` 即可 interface User { name: string; age: number; [k: string]?: any } const secondPeople: User = { name: "Tony', age: 24, }
提示:key 的 type 一般只有 string/number/symbol 这三个属性,对我而言 string 已经可以满足大部分需求了,少数情况才会用到 symbol,至于 number 那是少之又少了。
4. Record
Record
与 索引签名
很像,都是用于定义未知数量的 keyType: valueType
形式,我们来看下例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17type R = Record<string, { name: string; age: number; }> const Players: R = { one: { name: 'Jack', age: 20 }, two: { name: 'Tony', age: 21 }, // ... }
4.1 Record 与 索引签名有什么不同?
你觉得哪个能满足你的需求就用哪个。
5. infer
infer
从字面义理解起来比较抽象,但使用起来是比较简单,它就是一个提取类型
的作用,
而且要使用它的是有前提条件的,它是必须由extends
和 ?
构成组合 ,我们来看下例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20interface Animal { name: string; age: number; action: () => number } type GetType<T> = T extends { action: () => infer R} ? R : T /** 因为 Animal 里的 action 存在,所以提取 action => 里的返回值,并用 R 表示。 以下等同于 type getAnimalType = number */ type getAnimalType = GetType<Animal> /** 因为不存在 action 所以返回 T 以下等同于 type getAnimalType = boolean */ type getAnimalType = GetType<boolean>
infer
不仅可以提取类型,它还支持联合
1
2
3
4
5
6
7
8
9
10
11
12interface Animal { name: string; age: number; } type GetType<T> = T extends { name: infer R; age: infer R; } ? R : T /** 因为 Animal 里的 name 和 age 都存在,所以提取 name/age 的类型并用 R 表示。 以下等同于 type getAnimalType = string | age */ type getAnimalType = GetType<Animal>
6. typeof
typeof
可以用来复制某个变量里的类型声明
首先我们知道,当声明一个变量时,在没有指定类型的情况下 TS 会自动推导类型,比如
1
2
3
4
5const UserName = 'Jack' // TS 自动推导后 const UserName:string = 'Jack' // 如果你有用 vscode 编辑器可以用鼠标移动到这个变量就可以看到效果了
知道这点后,我们再来看看下面的例子就能懂了
1
2
3const UserName = 'Jack' const AnotherUser: typeof UserName = 'Tony'
再来举个例子:假如某个函数里有个参数需要复制某个对象里的声明类型,我们就可以这样做:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28const dog = { title: 'Dog can speaking' properties: { name: 'Duolo', type: 'Big', color: 'Yellow', }, actions: { say() { console.log('Hi, I am a human, not a dog at all.')}, } } // 现在这个 properties 默认推导成 properties: any 类型, function getPropertyByType(properties) { if (feature.name === 'Duolo') { dog.actions.say() } } // 如果我们要求 properties 传递进来的必须包含 dog.properties 类型, // 这时就可以用到 typeof function getPropertyByType(properties: typeof dog['properties']) { if (feature.name === 'Duolo') { dog.actions.say() } }
学完这几个关键字后,还需要不断去应用加深印象,
否则过不了多久还是会忘记,又得重新回来学习。
有错误欢迎指出,完!
最后
以上就是不安月饼最近收集整理的关于TypeScript & 详细解释 in、keyof、extends、索引签名、Record、typeof 的含义(不定时更新)的全部内容,更多相关TypeScript内容请搜索靠谱客的其他文章。
发表评论 取消回复