很早就听过 TypeScript,当时自己试了下觉得挺简单的,就是给数据声明一个类型提高可读和维护性,不过当时 TS 还不盛行,项目也没机会用到就一直落着,随着 TS 不断普及,许多项目都开始引入了 TS,这不最近在 github 看到一些 TS 代码后,心里开始吐槽:
这好好的非要给 JS 装饰的这么花里胡哨的?我都认不出来这是什么语言了,你确定这是提升 JS 语言可读性而不是添乱?有同学跟我一样的请举个手!
后来静下心来想了想,既来之则安之,与其吐槽,不如静下心来好好重温下 TS ,在这期间也提取了一些有必要掌握的关键字,现在再去读一些 TS 代码心里总算有底了,可以说理解这些关键字的含义,对大部分的 TypeScript 代码都能看得懂。
当然了,光看懂还不行,还得学会运用,但我们现在的目标还是先看懂吧~
提示:本文假设你已经认识一些基本的 TS 语法,如果是第一次接触 TS,建议看下我前面写过的 TypeScript 日常基本语法
keyof
可以获取对象里的所有 key ,跟 JS 中的 Object.keys()
类似,假如我们想要获取某个 interface 接口里的所有 key,就可以用到 keyof
,我们来看下例子:
interface User {
name: string;
age: number;
birthday: string;
}
// 下面等同于 type keysType = 'name' | 'age' | 'birthday'
type keysType = keyof User
const key:keysType = 'name'
看起来好像有点鸡助的样子,无非就是复制 key ,别急,它的真正作用在于搭配其它关键字,我们往下看就能知道了。
in
可以用来配合 keyof
,我们从上面得知 keyof
是获取所有的 key,假如我们想对每个 key 进行额外的处理怎么做呢?
这里就可以用到 in
关键字,我们来看下例子:
interface 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
带来的作用了?
in
单独使用的话跟 JS 中的 in 类似
// 表示 foo 定义的 key 必须包含 a 或 b 或 c
type Foo = {
[K in 'a' | 'b' | 'c']: number
}
const obj: Foo = {
a: 100,
b: 200,
c: 300,
d: 400 // 报错
}
从字面上理解 interface extends
好像就叫接口继承,你可以这么称呼,但我个人更喜欢叫接口合并
,我们来看例子。
interface Action {
bark: () => void
}
interface People extends Action{
name: string;
age: number;
}
// 上面的 People 等同于下面
interface Peoople {
name: string;
age: number;
bark: () => void
}
敲黑板:请忘记之前我们学过的继承概念,因为下来要讲的 extends 根本不叫继承。
从字面上理解 泛型约束
,什么是泛型约束?这里我总结一下就是:
泛型传递进来的类型必须满足 U 里的所有属性和类型
我们来看例子:
interface 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> = {}
通过例子可以得出,定义的泛型被限制住了,不能随便传递,我们再来举另外一个例子来加深印象:
interface 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
索引签名
指的是 [K : keyType]: valuetype
, 其中 K 可以随意命名,说的通俗点就是用来定义未知数量的 keyType: valueType
,我们来看下例子就懂了
interface 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 那是少之又少了。
Record
与 索引签名
很像,都是用于定义未知数量的 keyType: valueType
形式,我们来看下例子
type R = Record<string, {
name: string;
age: number;
}>
const Players: R = {
one: {
name: 'Jack',
age: 20
},
two: {
name: 'Tony',
age: 21
},
// ...
}
你觉得哪个能满足你的需求就用哪个。
infer
从字面义理解起来比较抽象,但使用起来是比较简单,它就是一个提取类型
的作用,
而且要使用它的是有前提条件的,它是必须由extends
和 ?
构成组合 ,我们来看下例子:
interface 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
不仅可以提取类型,它还支持联合
interface 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>
typeof
可以用来复制某个变量里的类型声明
首先我们知道,当声明一个变量时,在没有指定类型的情况下 TS 会自动推导类型,比如
const UserName = 'Jack'
// TS 自动推导后
const UserName:string = 'Jack' // 如果你有用 vscode 编辑器可以用鼠标移动到这个变量就可以看到效果了
知道这点后,我们再来看看下面的例子就能懂了
const UserName = 'Jack'
const AnotherUser: typeof UserName = 'Tony'
再来举个例子:假如某个函数里有个参数需要复制某个对象里的声明类型,我们就可以这样做:
const 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()
}
}
这几个其实理解起来很简单,就放在一起讲了,可以理解为 typeof
的升级版,带有条件性的复制。
Omit
排除 keys。Pick
指定 keys 。Patial
任意一种即可。除了
admin
字段,其它两个都可以,说白了就是复制 { name / age } 。
Pick 和 Omit 俩者是反向关系,因此我们也可以通过 Pick 来模拟 Omit 的功能,
即只摘取 {name,age} 不摘取 admin 。
例子:Partial
name/age/admin 任意一个字段均支持 ,其它则不行;这不就是传统的选项式嘛:
interface SuperUser {
name?: string;
age?: number;
admin?: boolean;
}
----------------------------------------------------------------结尾----------------------------------------------------------------
学完本文举例的关键字后,还要不断从项目中应用,以此加深印象,
否则过不了多久还会忘记,又得重新回来学习。
有错误欢迎指出,完!