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)
}

我自己想到了用 jsis 关键词去判断某个属性是否存在来具体区分这两个 interface ,但是这样好像已经到了 js 的层面了。

问题:在 TS 层面有没有可以实现的方式呢?

阅读 922
avatarAI BotBETA

在 TypeScript 中,你通常会使用类型守卫(Type Guards)来在函数体内判断参数的类型。类型守卫是一种表达式,它执行运行时检查并返回一个类型信息给 TypeScript 的类型系统。

你可以通过自定义类型守卫函数或者使用 in 关键字配合类型断言来实现这一点。但是,请注意,TypeScript 的类型系统只在编译时存在,运行时并不包含类型信息,所以类型守卫实际上是执行一些运行时的检查来模拟类型信息。

下面是一个使用 in 关键字和类型断言的示例:

interface Person {
    name: string;
    age: number;
}

interface Animal {
    food: string;
    kind: string;
}

function isPerson(some: any): some is Person {
    return 'name' in some && 'age' in some;
}

function isAnimal(some: any): some is Animal {
    return 'food' in some && 'kind' in some;
}

function test(some: Person | Animal) {
    if (isPerson(some)) {
        // 在这个分支中,TypeScript 知道 some 是 Person 类型
        console.log(some.name, some.age);
    } else if (isAnimal(some)) {
        // 在这个分支中,TypeScript 知道 some 是 Animal 类型
        console.log(some.food, some.kind);
    }
}

// 示例使用
const personObj: Person = { name: 'Alice', age: 30 };
const animalObj: Animal = { food: 'meat', kind: 'dog' };

test(personObj); // 输出 Alice 30
test(animalObj); // 输出 meat dog

在这个例子中,isPersonisAnimal 函数是自定义的类型守卫。它们接收一个 any 类型的参数,并返回一个布尔值,表示参数是否符合 PersonAnimal 类型。通过返回 some is Personsome is Animal,TypeScript 的类型系统会理解在这些函数内部的条件分支中,some 的确切类型是什么。

这样,你就可以在 test 函数内部安全地使用 some 对象的特定属性,而不用担心类型错误了。

2 个回答

话先说在头,其实TS官网教程类型收窄这章里,讲到typeof检查、in检查,已经能做到大部分情况的类型收窄了,一般业务开发没必要过于苛求运行时的检查。像题主的例子里,if ('name' in some) {}if ('food' in some) {}就能自动收窄PersonAnimal了。除非你对类型检查有很极致的追求。

以下正文。


interfacetype属于编译期行为,编译结束后是不存在的,所以你在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匿名对象的写法,可能需要一定时间才能适应。

ts 的 interface 在编译成 js 时会被删除,所以无法在运行时使用 interface 来做判断。

interface person{
name:string
age:number
}

interface animal {
food:string
kind:string
}


function test(some:person|animal){
  if('name' in some) {
    some
  // ?^ person
  }

  if('food' in some) {
    some
  // ?^ animal
  }
}
推荐问题
logo
Microsoft
子站问答
访问
宣传栏