TypeScript 如何在函数体中判断参数的类型?
我在项目中定义了两个 interface
,
interface person{
name:string
age:number
}
interface animal {
food:string
kind:string
}
接下有个函数是处理这两个类型的
function test(some:person|animal){
// TODO:
// 我想在这里判断 some 具体是什么然后分别处理
// 期望的伪代码:
if(some 的类型=== person){}
else if(some 的类型=== animal)
}
我自己想到了用 js
的 is 关键词去判断某个属性是否存在来具体区分这两个 interface
,但是这样好像已经到了 js
的层面了。
问题:在 TS 层面有没有可以实现的方式呢?
回复
1个回答

test
2024-07-20
话先说在头,其实TS官网教程类型收窄这章里,讲到typeof
检查、in
检查,已经能做到大部分情况的类型收窄了,一般业务开发没必要过于苛求运行时的检查。像题主的例子里,if ('name' in some) {}
和if ('food' in some) {}
就能自动收窄Person
和Animal
了。除非你对类型检查有很极致的追求。
以下正文。
interface
和type
属于编译期行为,编译结束后是不存在的,所以你在JS里是无法直接使用interface
来判断类型。解决办法,一种是写谓词函数(predicate)来手动检查。这种函数的返回值是value is SomeType
形式。
interface Person {
name: string
age: number
}
interface Animal {
food: string
kind: string
}
// 判断一个对象是否Person
function isPerson(o: unknown): o is Person {
const obj = o as Partial<Person> | null | undefined
if (
typeof obj?.name === 'string'
&&
typeof obj?.age === 'number'
) {
return true
} else {
return false
}
}
// 判断一个对象是否Animal
function isAnimal(o: unknown): o is Animal {
const obj = o as Partial<Animal> | null | undefined
if (
typeof obj?.food === 'string'
&&
typeof obj?.kind === 'number'
) {
return true
} else {
return false
}
}
function test(some: Person | Animal) {
if (isPerson(some)) {
// 现在some是Person
console.log(some)
}
if (isAnimal(some)) {
// 现在some是Animal
console.log(some)
}
}
看代码你也发现,这样手写predicate
非常繁琐,而且容易有遗漏。所以有一个工具库io-ts可以代替繁琐的工作。
import * as t from 'io-ts'
// 先定义运行时检查工具
const User = t.type({
name: t.string,
age: t.number
})
// 转化为type
type User = t.TypeOf<typeof User>
// 以下同上
const Animal = t.type({
food: t.string,
kind: t.string
})
type Animal = t.TypeOf<typeof Animal>
function test(some: User | Animal) {
if (User.is(some)) {
console.log('现在some是Person', some)
}
if (Animal.is(some)) {
console.log('现在some是Animal', some)
}
}
test({ name: 'tom', age: 3 })
test({ kind: 'cat', food: 'fish' })
在TS里,class
既是type也是value,所以,还有一种方法,就是干脆用class
创建对象,然后用instanceof
检查原型链判断类型
class Person {
name: string
age: number
constructor(init: Person) {
Object.assign(this, init)
}
}
class Animal {
food: string
kind: string
constructor(init: Animal) {
Object.assign(this, init)
}
}
function test(some: Person | Animal) {
if (some instanceof Person) {
console.log('现在some是Person', some)
}
if (some instanceof Animal) {
console.log('现在some是Animal', some)
}
}
// 必须要用构造函数来创建class实例
test(new Person({ name: 'tom', age: 3 }))
test(new Animal({ kind: 'cat', food: 'fish' }))
这就是很经典的OOP
开发方式。如果此前习惯传统JS匿名对象的写法,可能需要一定时间才能适应。
回复

适合作为回答的
- 经过验证的有效解决办法
- 自己的经验指引,对解决问题有帮助
- 遵循 Markdown 语法排版,代码语义正确
不该作为回答的
- 询问内容细节或回复楼层
- 与题目无关的内容
- “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容