类型守卫
在写函数的时候容易碰到一个这样的场景,输入不同类型的参数进去,函数内部做的一些逻辑只能针对某种变量类型,这时就需要用条件去做判断了:
// 例如:把一个字符串或者数组内的所有子项都转成大写
let convertToUpperCase = (strOrArray: string | string[]) => {
if (typeof strOrArray === 'string') {
return strOrArray.toUpperCase();
} else if (Array.isArray(strOrArray)) {
return strOrArray.map(item => item.toUpperCase());
}
}
通过条件做类型的判断就是其中一种类型守卫。
我们还可以通过switch
、typeof
、===
、instanceof
等等语句作类型守卫。
这里就专门讲下对付对象类型的守卫怎么写,用in
关键字:
interface Dog {
wang: string;
}
interface Cat {
miao: string;
}
const getName = (animal: Dog | Cat) => {
if ('wang' in animal) {
return animal.wang;
} else if ('miao' in animal) {
return animal.miao;
}
}
还需要提一个类型谓词 is
:
const isDog = function (animal: Dog | Cat): animal is Dog { // 通过is来指定看为Dog类型
return "wang" in animal;
};
const getName = (animal: Dog | Cat) => {
if (isDog(animal)) { // 然后这个函数还可以用在专门来判断联合类型中是否是具体某个类型
return animal.wang;
}
};
注意:此时的
is
不能用as
类型断言来代替,会报错。
类型兼容
先跳过…
keyof
可以拿到interface的所有键,组成联合类型。
interface P{
x: number;
y: number;
}
type keys = keyof P; // 遍历出来 "x" | "y"
结合泛型约束可以实现个不错的功能,在符合ts要求下,通过输入对象和key:
function get<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
拓展(看不懂也没关系,因为Ts已经给我们提供了工具类型):
interface User {
id: number;
age: number;
name: string;
};
type Partial<T> = { // 就是把接口内容都转成可选的
[P in keyof T]?: T[P];
};
type PartialUser = Partial<User> // 相当于 = { id?: number; age?: number; name?: string; }
type Pick<T, K extends keyof T> = { // 就是单独拿出指定内容
[P in K]: T[P];
};
type PickUser = Pick<User, "id" | "age"> // 相当于 = { id: number; age: number; }
typeof
和js的功能不一样,返回的是当前变量的类型
let obj = { a: '1' }
type A = typeof obj
?与 &&
const err = new Error();
err.stack.split('\n'); // 因为Error中的stack是个可选属性,所以这样直接使用ts会报‘对象可能为“未定义”。
// 使用?号
err.stack?.split('\n'); // 相当于 err.stack && err.stack.split('\n');
!用法
这里直接引用文章TypeScript中的问号 ? 与感叹号 ! 的含义的代码
重新定义属性特性:
// 因为接口B里面name被定义为可空的值,但是实际情况是不为空的,那么我们就可以
// 通过在class里面使用!,重新强调了name这个不为空值
class A implemented B {
name!: string
}
interface B {
name?: string
}
强制链式调用:
// 我们确信这个字段100%出现,那么就可以添加!,强调这个字段一定存在,ts不要对他进行类型校验了
new Error().stack!.split('\n');
注释
TS的注释可以让编辑器识别,然后通过按住ctrl键鼠标移入注释的关键字后就会有浮窗显示注释:
/** 这是一个人的接口定义 */
interface Person {
/** 人的姓名 */
name: string,
}
const p: Person = { // 按住ctrl键鼠标移入显示 “这是一个人的接口定义”
name: 'cool' // 按住ctrl键鼠标移入显示 "人的姓名"
}
工具类型
ts提供了很多定义好的工具类型供我们使用,叫做utility type。下面积累几个例子。
Parameters
场景举例,在一个函数Fn1中你需要返回一个全新的函数Fn3,但是Fn3函数的入参类型是另外一个函数Fn2的入参类型,那么可以这样写:
Fn1 = () => {
return (...[a, b]: Parameters<typeof Fn2>) => {] // typeof 表示获取Fn2的入参类型,然后通过泛型传入到Parameters中,Parameters就会帮我们提取Fn2的入参类型,作为...[a, b]入参的约束
}
typeof不是js的类型判断,是ts代码编写时期的类型获取,不会真正参与编译
Partial
场景举例,当我们定义了一个接口或者类型别名
type Person = {
name: string,
age: number,
...
}
然后要用于一个变量的约束,但是这个变量只需要定义age,就可以用这个工具类型
let xiaoming: Partial<Person> = {age: 18} // 不会报错
一般都用于节省接口的部分重复定义。
当然,函数入参就不需要这个工具类型也能支持,具体可以看【TS基础】个人学习记录3-学习函数、箭头申明、参数、this、重载
不过这个Partial的功能就显得很不严格了,还是上面的例子,变量也可以直接赋值{}
,而且类型约束变得很随意,只要小于等于Person的内容即可。
所以,可以用另一个工具类型去解决。
Omit
场景举例,当我们定义了一个接口或者类型别名
type Person = {
name: string,
age: number
}
然后要用于一个变量的约束,但是这个变量只需要定义age,而且必须要严格限定类型不能和Partial一样,就可以用这个工具类型。
let xiaoming: Omit<Person, 'name'> = {age: 18} // 表示使用删除了name属性的Person作为约束
如果删除的接口属性比较多,可以这样子:
let xiaoming: Omit<Person, 'name' | 'age'> = {} // 表示使用删除了name,age属性的Person作为约束
Pick
场景举例,当我们定义了一个接口或者类型别名
type Person = {
name: string,
age: number
}
然后要用于一个变量的约束,但是这个变量只需要定义age,就可以用这个工具类型。
let xiaoming: Pick<Person, 'age'> = {age: 18} // 表示使用只提取了age属性的Person作为约束
也可以多个提取
let xiaoming: Pick<Person, 'age', 'name'> = {}
Exclude
用于联合类型的内容剔除
type Person = {
name: string,
age: number
}
type P = keyof Person
type Age = Exclude<P, 'name'> // 意思就是把联合类型里的name剔除了
Record
type TKeys = 'A' | 'B'
interface IInfo {
name:string,
age?: number,
}
type TRecord = Record<TKeys, IInfo>
// 等同于
type TRecord = {
A: IPeople;
B: IPeople;
}