TS 类型守卫
1. typeof
1.1 typeof 的类型守卫用法
在类型代码中, typeof
执行的是最窄推导程度, 具体如下
const str = '123'
type type1 = typeof str // '123'
通过类型推导, 可以达到收窄变量类型的目的
const foo = (input: string | number) => {
if (typeof input === 'string') { // input 是 string 类型
console.log(input.charAt(0))
} else if (typeof input === 'number') { // input 是 number 类型
console.log(input.toFixed(2))
} else {
throw new Error('type error')
}
}
foo('123') //=> '1'
foo(233) //=> 233.00
1.2 typeof 的类型守卫函数
关于类型守卫函数, 需要注意的是 is
, 在 ReturnType
处填写 oneParam + is + Type
, 如下
const isString = (input: unknown): input is string => typeof input === 'string'
const isNumber = (input: unknown): input is number => typeof input === 'number'
const fn = (input: string | number) => {
if (isString(input)) {
// input 是 string 类型
console.log(input.charAt(0))
} else if (isNumber(input)) {
// input 是 number 类型
console.log(input.toFixed(2))
} else {
throw new Error('type error')
}
}
fn('123') //=> '1'
fn(123) //=> 123.00
需要注意的是, 只要你返回的是布尔值, 类型守卫函数就会直接判断是否是 Type
类型, 而不管你这个布尔值是怎么来的, 所以当逻辑与类型不匹配时就很容易出错, 具体如下(这段代码在实际运行时会报错):
const isString = (input: unknown): input is string => typeof input === 'number'
const isNumber = (input: unknown): input is number => typeof input === 'string'
const fn = (input: string | number) => {
if (isString(input)) {
// input 其实是 number 类型, 无法调用 charAt 方法
console.log(input.charAt(0))
} else if (isNumber(input)) {
// input 其实是 string 类型, 无法调用 toFixed 方法
console.log(input.toFixed(2))
} else {
throw new Error('type error')
}
}
fn('123')
fn(233)
1.3 关于 typeof 需要注意的点
需要注意的是, typeof
只有在应用于变量上时会有类型守卫效果, 而应用在变量的属性上时是没有类型守卫的效果的, 如下
interface Foo {
id: number
age: number
}
interface Bar {
id: string
gender: string
}
const fn = (input: Bar | Foo) => {
if (typeof input.id === 'string') {
console.log(input.gender) // 报错, 没有起到类型收窄的作用
} else if (typeof input.id === 'number') {
console.log(input.age) // 报错, 没有起到类型收窄的作用
} else {
throw new Error('type error')
}
}
但是这可以通过类型守卫函数解决, 之前也提到过: 类型守卫函数只需要返回布尔值即可帮助进行类型守卫, 至于这个布尔值是怎么来的无所谓. 代码如下:
interface Foo {
id: number
age: number
}
interface Bar {
id: string
gender: string
}
const isFoo = (input: Bar | Foo): input is Foo => typeof input.id === 'number'
const isBar = (input: Bar | Foo): input is Bar => typeof input.id === 'string'
const fn = (input: Bar | Foo) => {
if (isBar(input)) {
console.log(input.gender)
} else if (isFoo(input)) {
console.log(input.age)
} else {
throw new Error('type error')
}
}
fn({id: 123, age: 234}) //=> 234
fn({id: '123', gender: 'female'}) //=> female
有关属性的类型守卫是于 in
有关的, 此外还有一种方法: 通过对象的字面量进行判断, 如下:
interface Foo {
id: number
name: string
check: 'foo'
}
interface Bar {
id: string
gender: string
check: 'bar'
}
const fn = (input: Bar | Foo) => {
if (input.check === 'foo') {
console.log(input.name) // input 类型是 Foo
} else if (input.check === 'bar') {
console.log(input.gender) // input 类型是 Bar
} else {
throw new Error('type error')
}
}
fn({id: 1, name: 'saber', check: 'foo'}) //=> saber
fn({id: '1', gender: 'female', check: 'bar'}) //=> female
2. in
2.1 in 的类型守卫用法
当用于判断的属性是预期类型的独有属性时, 就可以实现类型收窄
interface Foo {
id: number
name: string
}
interface Bar {
id: string
gender: string
}
const f = (input: Bar | Foo) => {
if ('name' in input) {
// 由于 'name' 属性是 Foo 类型独有的属性, 故而可以实现类型收窄
console.log(typeof input.id === 'number')
} else if ('gender' in input) {
// 由于 'gender' 属性是 Bar 类型独有的属性, 故而可以实现类型收窄
console.log(typeof input.id === 'string')
} else {
throw new Error('type error')
}
}
f({id: 123, name: 'saber'}) //=> true
f({id: '123', gender: 'female'}) //=> true
理所当然的, 如果用于判断的属性不是预期类型的独有属性, 则无法进行类型收窄, 如下(这段代码无法通过编译):
interface Foo {
id: number
name: string
}
interface Bar {
id: string
gender: string
}
const f = (input: Bar | Foo) => {
if ('id' in input) {
console.log(typeof input.id === 'number')
} else {
// Property 'id' does not exist on type 'never'.
// 当把具有 id 属性的对象过滤掉后, 就只剩下 never 类型了
console.log(typeof input.id === 'string')
}
}
3. instanceof
3.1 instanceof 类型守卫的使用
class FooBase {}
class BarBase {}
class Foo extends FooBase {
fooOnly() {}
}
class Bar extends BarBase {
barOnly() {}
}
function handle(input: Foo | Bar) {
if (input instanceof FooBase) {
input.fooOnly()
} else {
input.barOnly()
}
}