Published on

TypeScript - Typeof Narrowing

Authors
  • avatar
    Name
    Deng Hua
    Twitter

目录

Typeof Guards

类型缩小是指一种非常常见的情况,我们有一些不太精确的类型,比如说联合类型, 我们需要将其缩小到更精确的类型。

function triple(value: number | string) {
  return value.length // 类型“string | number”上不存在属性“length”。
}

triple函数接收一个类型为numberstring联合类型的参数,返回参数的.length属性。 当时我们看到,TS报错了,原因是此时参数是联合类型,我们需要做类型收窄。

在TS中使用类型收窄,最简单的方法就是使用Typeof Guards类型守卫。

typeof Type Guards involve simply doing a type check before working with a value. Since unions allow multiple types for a value, we can first check what came through before working with it

function triple(value: number | string) {
  if (typeof value === 'string') {
    return value.length
  }
}

现在TS知道,在if语句中,value只可能是string类型。

并且TS也能分析上下文,在条件语句外也能推断参数类型。

function triple(value: number | string) {
  if (typeof value === 'string') {
    return value.length
  }

  return value.toFixed() // 此处value只可能为number类型
}

这是类型守卫的一个例子。

Truthiness Guards

Truthiness Type Guards involve checking a value for being truthy or falsy before working with it. This is helpful in avoiding errors when values might be null or undefined.

还有另一种缩小类型的方法,使用真假值验证。

const printLetters = (word: string | null) => {
  if (!word) {
    console.log('No word was provided.')
  } else {
    // only loop if word exists / is truthy
  }
}

Equality Narrowing

使用相等运算符缩小类型

equality Type Guards involve comparing types to each other before doing certain operations with values. By checking two values against one another, we can be sure they're both the same before working with them in a type-specific way

严格相等

function someDemo(x: string | number, y: string | boolean) {
  if (x === y) {
    // x and y only be string type
    x // (parameter) x: string
    y // (parameter) y: string
  }
}

非严格相等

function someDemo(x: string | number, y: string | boolean) {
  if (x == y) {
    x.toUpperCase()
  }
}

someDemo(3, '3')

注意,使用非严格相等时,这涉及隐式类型转换,TS不会提示,尽量使用===全等操作符。

Narrowing With The In Operator

Javascript's in operator helps check if a certain property exists in an object. This means we can use it to check if a value exists in an object, according to its type alias or aliases, before working with it

使用in运算符进行类型缩小

in运算符用于查看一个特定的属性是否存在于一个对象中,使用in的原因是,很多时候我们会使用interfacetype alias 无法使用typeof

来看一个例子

interface Movie {
  title: string
  duration: number
}
interface TVShow {
  title: string
  numEpisodes: number
  episodeDuration: number
}

function getRuntime(media: Movie | TVShow) {
  if (typeof ) // you can't
}

我们无非通过使用typeof来确定media是哪种接口,这时候可以使用in运算符。因为有一些属性是TVShow接口特有的。

interface Movie {
  title: string
  duration: number
}
interface TVShow {
  title: string
  numEpisodes: number
  episodeDuration: number
}

function getRuntime(media: Movie | TVShow) {
  if ('numEpisodes' in media) {
    media // (parameter) media: TVShow
  }
}

再看一个type alias的例子

type Cat = {
  meow: () => void
}
type Dog = {
  bark: () => void
}

const talk = (creature: Cat | Dog) => {
  if ('meow' in creature) {
    return creature.meow()
  }

  return creature.bark()
}

Instanceof Narrowing

instanceof is a Javascript operator that allows us to check if one thing is an instance of another (remember prototypes?). This can help us narrow types when working with things like classes.

const printFullDate = (date: Date | string) => {
  if (date instanceof Date) {
    return date.toUTCString()
  }

  return new Date(date).toUTCString()
}

另一个例子

class User {
  constructor(public username: string) { }
}
class Company {
  constructor(public name: string) { }
}

function printName(entity: User | Company) {
  if (entity instanceof User) {
    entity // (parameter) entity: User
  }
}

Working With Type Predicates(类型谓词)

Typescript allows us to write custom functions that can narrow the type of a value. These functions have a very special return type called a type predicate. A predicate takes the form parameterName is Type

类型谓词可以告诉typescript一个值是否属于特定类型。

来看一个例子

makeNoise函数接收一个animal类型参数,返回一个字符串。

interface Cat {
  name: string
  numLives: number
}

interface Dog {
  name: string
  breed: string
}

function makeNoise(animal: Cat | Dog): string {
  // how do i known it's cat or dog?
}

在函数内,我们如何知道animalCat还是Dog呢?之前我们已经知道了in操作符,但在这里我们可以创建一个可重用的函数,可以在任何地方使用它。

function isCat(animal: Cat | Dog): boolean {
  // ...
}

这时候,我们需要通过numLives属性来确认是否是Cat,但此时animal是联合类型,只能访问共有的属性或方法。 此时我们需要“假定”animal一定是Cat类型,才能访问numLives属性,这里用到了类型断言,语法为: as;

function isCat(animal: Cat | Dog): boolean { // 返回布尔类型
  return (animal as Cat).numLives !== undefined
}

function makeNoise(animal: Cat | Dog): string {
  if (isCat(animal)) { // TS不知道这里的true或false是什么含义
    animal  // (parameter) animal: Cat | Dog
  }
}

但这里还有一个问题,isCat只返回boolean类型,只有truefalse两个值,TS并不知道这个truefalse表示什么? 所以在if语句内,我们查看animal的类型,会显示还是(parameter) animal: Cat | Dog,TS还是不知道animal的类型。

这时候就需要用到类型谓词了

function isCat(animal: Cat | Dog): animal is Cat {
  return (animal as Cat).numLives !== undefined
}

现在TS可以正确的识别类型收窄后的animal了。

function isCat(animal: Cat | Dog): animal is Cat {
  return (animal as Cat).numLives !== undefined
}

function makeNoise(animal: Cat | Dog): string {
  if (isCat(animal)) {
    animal // (parameter) animal: Cat
  } else {
    animal // (parameter) animal: Dog
  }
}