Typescript: 类型保护之类型断言、类型谓词
在使用 ts 过程中,对于新手最怕出现 ts 告警,其中很多是因为类型保护导致的,本文梳理了关于类型保护的几种解法,欢迎阅读。
类型保护
首先通过一个例子来了解什么是类型保护。
interface Bird {
fly: boolean
sing: () => {}
}
interface Dog {
fly: boolean
bark: () => {}
}
function trainAnimal(animal: Bird | Dog) {
animal.sing() // 提示错误
}
当我访问参数 animal.sing 的时候,报如下错误:

这是因为ts并不知道 animal 到底有没有 sing 方法,因为它有可能是 Dog。
像这种通过联合类型定义的一个变量,当我们访问这个变量的时候,只会提示公共的属性和方法。上面那个例子,只有fly是两者共有的,所以可以访问animal.fly没有问题。
如果你直接访问animal.sing,就会报错,要避免这种报错,就需要确定它的类型,这个确定的过程就叫类型保护。
那我们可以通过什么方式来做类型保护呢?
类型断言 as
对于上面的例子,我们可以使用类型断言进行类型保护,改造如下:
function trainAnimal(animal: Bird | Dog) {
if ((animal as Bird).sing) {
;(animal as Bird).sing()
} else if ((animal as Dog).bark) {
;(animal as Dog).bark()
}
}
当访问animal时,首先利用as进行断言成某一个类型,然后判断这个类型是否存在某个属性,这样就可以放心访问animal.sing了。
再来看一个例子,sameValue 是 any 类型,但是我知道它其实是个字符串类型,那么也可以使用类型断言成字符串类型
let sameValue: any = 'this is a string'
// 推荐使用这种语法
let strLength: number = (sameValue as string).length
// 还可以这么写,进行强制转换
let strLength: number = (<string>sameValue).length
除了类型断言外,还有其他的几种方式可以进行类型保护。
关键字 typeof instanceof in 类型保护
typeof
如果变量是基本类型而不是复杂类型,可以直接使用 typeof 来做类型保护,这样比类型断言简洁一些。
function add(first: string | number, second: string | number) {
if (typeof first === 'string' || typeof second === 'string') {
return `${first}${second}`
} else {
return first + second
}
}
instanceof
instanceof 操作符是 JS 中的原生操作符,用来判断一个实例是不是某个构造函数创建的,或者是不是使用 es6 语法的某个类创建的。在 ts 中,使用 instanceof 操作符也可以达到类型保护的效果。
class Fish {
swim() {
console.log('swim')
}
eat() {}
}
class Mouse {
miao() {
console.log('miao')
}
eat(){}
}
function getRandPet () : Fish | Mouse {
return Math.random() > 0.5 ? new Fish() : new Mouse()
}
const petName = getRandPet()
if (petName instanceof Fish) {
petName.swim()
}
if (petName instanceof Mouse) {
petName.miao()
}
通过petName instanceof Fish对petName类型进行了保护,这样访问petName.swim就不会报错。
in
还是文章开头的那个例子,下面用 in 来进行类型保护:
function trainAnimal(animal: Bird | Dog) {
if ('sing' in animal) {
animal.sing()
} else {
animal.bark()
}
}
可以看到这比上面所有的类型保护写法更加简洁。
以上三种类型保护,在开发过程中灵活使用就可以了。
类型谓词 is
下面我们来看一类特殊的类型保护:类型谓词 is。下面通过一个例子来看看什么是类型谓词。
下面的代码 ts 会直接报错,因为unknown类型上没有toUpperCase方法。
function upperCase(str: unknown) {
// 类型unknown上不存在属性toUpperCase,所以报错
str.toUpperCase()
}
那怎么修改呢?我们可以定义一个判断是否为字符串类型的函数isString
function isString(s: unknown): boolean {
return typeof s === 'string'
}
function upperCase(str: unknown) {
if (isString(str)) {
str.toUpperCase() // 报错
}
}
我们发现依然报错,我们虽然判断了参数str是string类型, 但参数str的类型还是unknown。

也就是说这个条件判断并没有更加明确str的具体类型。
此时,可以在判断是否为string类型的函数返回值类型使用is关键词,即类型谓词。
// 判断参数是否为string类型, 返回布尔值
function isString(s:unknown):s is string{
return typeof s === 'string'
}
// 判断参数是否为字符串,是在调用转大写方法
function upperCase(str:unknown){
if(isString(str)){
str.toUpperCase()
}
}
可以看到,此时是str是一个string类型。

类型谓词的主要特点是:
- 定义一个函数,返回类型谓词,如
s is string; - 包含可以准确确定给定变量类型的逻辑语句,如
typeof s === 'string'。
一般类型谓词用在判断变量是什么类型:
const toString = Object.prototype.toString
/**
* 判断是否是日期对象
* @param val
* @returns
*/
export function isDate(val: any): val is Date {
return toString.call(val) === '[object Date]'
}
/**
* 判断是否是普通对象
* @param val
* @returns
*/
export function isPlainObject(val: any): val is Object {
return toString.call(val) === '[object Object]'
}
/**
* 判断是否是表单类型数据
* @param val
* @returns
*/
export function isFormData(val: any): val is FormData {
return typeof val !== 'undefined' && val instanceof FormData
}
/**
* 判断是否是URLSearchParams类型
* @param val
* @returns
*/
export function isURLSearchParams(val: any): val is URLSearchParams {
return typeof val !== 'undefined' && val instanceof URLSearchParams
}
总结
关于类型保护本文介绍几种解法:
-
类型断言
as:通过断言确定变量是某一种类型,然后有针对性的进行访问 -
关键字
in、typeof、instanceof:一种更为简洁的类型保护,利用了 ES 本身的语法进行判断 -
类型谓词
is:用来判断某个变量属于什么类型
转载自:https://juejin.cn/post/7244840710271877178