TS 的类型系统为我们的代码提供健壮性,可维护性的保障。
当然除去一些基本类型外,我们有时也需要表达一些复杂的类型,那这个时候灵活使用一些关键字
就很方便了。
这篇文章,就拿出一些可以方便我们进行类型表达的关键字来看一看。
extends
extends
在类型表达时,有下面两种用法:
- 用于类型的继承
interface Person {
name: string;
age: number;
}
interface Player extends Person {
item: 'ball' | 'swing';
}
复制代码
- 判断是否是能赋值给另一个类型
// 如果 T 可以满足类型 Person 则返回 Person 类型,否则为 T 类型
type IsPerson<T> = T extends Person ? Person : T;
复制代码
typeof
在 TS 中用于类型表达时,typeof
可以用于从一个变量上获取它的类型。
举一个没有卵用的例子:
const value: number = 10;
const value2: typeof vlaue = 100; // const value2: number
复制代码
但是请注意下面这种情况:
const value = 10;
const value2: typeof vlaue = 100;
// const value2: 10
// ERROR: Type '100' is not assignable to type '10'
复制代码
经测试,number
string
boolean
类型在没有声明而直接赋值的情况下,都会存在这个问题
举个有点用的例子
对于对象,数组,函数类型来讲,这个还是有点用的。参考
const data = {
value: 123,
text: 'text',
subData: {
value: false
}
};
type Data = typeof data;
// type Data = {
// value: number;
// text: string;
// subData: {
// value: boolean;
// };
// }
复制代码
keyof
keyof
是TS中的索引类型查询操作符。keyof T
会得到由 T
上已知的公共属性名组成的联合类型。
Keyof T, the index type query operator. For any type T, keyof T is the union of known, public property names of T
举个例子:
interface Person {
name: string;
age: number;
phoneNum: number;
}
type PersonProperty = keyof Person;
// type PersonProperty = "name" | "age" | "phoneNum"
复制代码
keyof
在我们限制类型或者枚举属性时还是非常常见的,比如下面这个小例子:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
复制代码
这样当我们尝试获取不在目标对象上的属性值时,TS会为我们检查到这样简单的错误
T[K]
在TS里称作索引访问操作符(indexed access operator
)。它可以为我们准确解析目标对象上的对应属性的正确类型。
在下面的介绍,我们可以继续看到 keyof
的应用。
in
in
操作符用于遍历目标类型的公开属性名。类似 for .. in
的机制。
从其用途看,我们很容易想到 in
可用于联合类型或者枚举类型。
使用于枚举类型
我们可以像下面这样使用枚举类型:
enum Letter {
A,
B,
C,
}
type LetterMap = {
[key in Letter]: string;
}
// type LetterMap = {
// 0: string;
// 1: string;
// 2: string;
// }
复制代码
使用于联合类型
我们可以像下面这样使用联合类型:
type Property = 'name' | 'age' | 'phoneNum';
type PropertyObject = {
[key in Property]: string;
}
// type PropertyObject = {
// name: string;
// age: string;
// phoneNum: string;
// }
复制代码
利用可用于联合类型的特性,我们有下面这种很常见的做法(仅举例):
type ToString<T> {
[key in keyof T]: string;
}
复制代码
使用于基础类型
值得一提的是,一些基础类型(string
, number
, symbol
)也可以用于 in
操作符:
type StringKey = {
[key in string]: any;
}
// type StringKey = {
// [x: string]: any;
// }
type NumberKey = {
[key in number]: any;
}
// type NumberKey = {
// [x: number]: any;
// }
type SymbolKey = {
[key in symbol]: any;
}
// 这里和预想的不一样 TODO
// type SymbolKey = {}
复制代码
关于这一点,我认为和TS中,string
与 number
是唯二两个可以做 索引签名 的类型是一致的
interface PersonArray {
[index: number]: Person;
}
interface PlainObject {
[key: string]: any;
}
复制代码
infer
关于 infer
操作符,这个可以用来进行类型推测。举个简单的小例子:
在 TS 中,如果我们在 generator
函数中使用了 yield
表达式,我们就会丢失类型。比如下面这样:
function returnSomething() {
// return something;
}
function* task() {
// 这里的 result 在TS中是没有拿到正确的函数返回类型的,大家可以试一下
const result = yield returnSomething();
}
复制代码
那为了解决类似的问题,TS 为我们内置了 ReturnType
的映射类型:
function* task() {
// 这里的 result 在TS中是没有拿到正确的函数返回类型的,大家可以试一下
const result: ReturnType<typeof returnSomething> = yield returnSomething();
}
复制代码
那我们来看一下 ReturnType
是如何做的呢,其实很简单,就是用到了 infer
:
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
复制代码
infer P
中的 P
即是表示待推断的返回值类型。
关于 infer
的更多内容,大家可以参考这篇文章,也希望大家可以多多支持作者
is
is
操作符用于TS的类型谓词中,是实现TS类型保护的一种方式(关于什么是类型保护)。
比如下面这种场景:
function doSometing(value: string | number) {
if (typeof value === 'string') {
// TS 可以识别这个分支中 value 是 string 类型的参数(这就叫类型保护)
// do something
} else {
// TS 可以识别这个分支中 value 是 number 类型的参数
// do something
}
}
复制代码
除去上面这种方式以外,我们可以使用TS的类型谓词来实现:
/**
* 此函数用于判断参数 value 是不是 string 类型
*
* 由于返回类型声明了类型谓词,可以帮助TS在代码分支中进行类型保护(默认返回 boolean 类型是没办法做到的)
**/
function isString(value: any): value is string {
return typeof value === 'string';
}
function doSometing(value: string | number) {
if (isString(value)) {
// TS 可以识别这个分支中 value 是 string 类型的参数(这就叫类型保护)
} else {
// TS 可以识别这个分支中 value 是 number 类型的参数
}
}
复制代码
这样做的好处是:实现了代码复用,实现了更好的语义化。
其实,TS 代码中 Array.isArray
便是使用了这样的声明。
interface ArrayConstructor {
// ...
isArray(arg: any): arg is Array<any>;
}
复制代码