TypeScript类型缩小

类型缩小的概念

前面我们写了一些这样的代码:

function padLeft(padding: number | string, input: string): string {
  if (typeof padding === 'number') {
    return ' '.repeat(padding) + input
  }
  return padding + input
}

没有if判断时,无法执行语句return ’ '.repeat(padding) + input,使用if判读之后就可以执行了,这就是一个TypeScript类型缩小。
JavaScript 的运行时控制流结构(如 if/else、条件三元组、循环、真值检查等)都会影响TypeScript对这些类型的判断。换句话说,TypeScript会对代码语句进行检查,遵循我们的程序采用可能执行路径得出的更具体的可能类型。

typeof 类型保护

typeof一般返回的类型有:string、number、boolean、object、undefined、function、bigInt、symbol。TypeScript可以理解这些类型的缩小。
注意:在JavaScript中,null也算object类型。
在下面的例子中,我们企图先判断strs是否为对象类型,如果是的话就直接遍历,但是忘了string[]和null在JavaScript中都算object类型,故进入第一个判断的参数可能是string[]也可能是null类型,而null类型无法进行遍历,故会报错。

function printAll(strs: string | string[] | null) {
  if (typeof strs === 'object') {
    for (const s of strs) {//error strs可能是null类型
      console.log(s)
    }
  } else if (typeof strs === 'string') {
    console.log(strs)
  } else {
  }
}

真值缩小

if 这样的构造首先可以将条件、&&、||、if 语句、布尔否定 (!) 等 “强制转换” 到 boolean 来理解它们,然后根据结果是 true 还是 false 来选择它们的分支。
例如:0、NaN、0n(bigInt版本的0)、null、“ ”、undefined都会被强制转换为false。其他值都会被转换为true。
以上是推断为类型缩小的字面布尔类型true、false。你也可以直接使用Boolean函数来将值强制为Boolean类型。

Boolean("hello"); // type: boolean, value: true
!!"world"; // type: true,    value: true  !!会将一个值转换为其对应的字面布尔类型。

在if中使用条件来进行真值缩小非常常见。例如上面那个例子,我们只要在if语句中判断strs是否为null并上类型为object,就能将参数判断为数组。

function printAll(strs: string | string[] | null) {
  if (strs && typeof strs === 'object') {
    for (const s of strs) {
      console.log(s)
    }
  } else if (typeof strs === 'string') {
    console.log(strs)
  } else {
  }

也可以先判断strs是否为null,再判断它是string类型还是object类型(string[])。

function printAll(strs: string | string[] | null) {
  if (strs) {
    if (typeof strs === "object") {
      for (const s of strs) {
        console.log(s);
      }
    } else if (typeof strs === "string") {
      console.log(strs);
    }
  }
}

带有否定分支的话就从否定分支中过滤掉。

function multiplyAll(values: number[] | undefined, factor: number): number[] | undefined {
  if (!values) {
    return values
  } else {
    return values.map((x) => x * factor)
  }
}

相等性缩小 使用switch !== === != ==

我们上面所举的printAll的例子没有处理str等于null的情况。相等性缩小可以处理。

function printAll(str: string | string[] | null) {
  if (str !== null) {
    if (str === 'string') {
      console.log(str)
    } else if (str === 'object') {
      for (const s of str) {
        console.log(s)
      }
    }
  }
}

JavaScript 对 == 和 != 的更宽松的相等性检查也正确地缩小了类型。
检查某物 == null 是否实际上不仅检查它是否是值 null,它还检查它是否可能是 undefined。这同样适用于 == undefined。
注意下面示例使用的是!=号,可以同时将null和undefined的情况过掉。

interface Container {
  value: number | null | undefined
}
function test(container: Container) {
  if (container.value != null) {
    console.log(container.value)
  } else {
    console.log('container.value可能是null或undefined')
  }
}

in运算符缩小

使用代码:“value” in x。其中 “value” 是字符串字面,x 是联合类型。判断联合类型中是否含属性名为value的类型。

type Fish = { swim: () => void }
type Bird = { fly: () => void }
function move(animal: Fish | Bird) {
  if ('swim' in animal) {
    return animal.swim()
  }
  return animal.fly()
}

其中属性名必须带引号,函数可以是箭头函数也可以是普通函数。
可选属性存在于两侧进行缩小。这样的类型判断和我们写c语言的逻辑是一样的,符合要求的类型就会进入if判断中。

type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Human = { swim?: () => void; fly?: () => void };
function move(animal: Fish | Bird | Human) {
  if ("swim" in animal) {
    animal;  (parameter) animal: Fish | Human
  } else {
    animal;  (parameter) animal: Bird | Human
  }
}

instanceof缩小

在 JavaScript 中,x instanceof Foo 检查 x 的原型链是否包含 Foo.prototype。

function logValue(x: Date | string) {
  if (x instanceof Date) {//判断x中是否含有Date原型对象
    x.toLocaleDateString()//Date的方法
  } else {
    x.toLocaleUpperCase()//string的方法
  }
}

赋值

变量被赋值之后类型可能会缩小。
给变量x定义string|number类型。
在这里插入图片描述
对变量重新赋值一个1,此时中间的x还是string|number类型,但是再打印x时,他就变成了number类型。
在这里插入图片描述
再次给x赋值一个字符串,第三个x仍然是string|number类型,但是第四个打印的x被缩小为string类型。
在这里插入图片描述
变量的声明类型为string|number,中途我们可以随意地将string或者number类型赋值给x,因为TypeScript始终以声明类型来判断变量的可赋值性。

控制流分析

当分析一个变量时,控制流可以一次又一次地分裂和重新合并,并且可以观察到该变量在每个点具有不同的类型。

function example() {
  let x: string | number | boolean
  x = Math.random() < 0.5 //let x: string | number | boolean
  console.log(x) //let x: boolean
  if (Math.random() < 0.5) {
    x = 'hello' //let x: string | number | boolean
    console.log(x) //let x: string
  } else {
    x = 100 //let x: string | number | boolean
    console.log(x) //let x: number
  }
  return x //let x: string | number
}

类型谓词

interface Fish {
  swim: () => void
}
interface Bird {
  fly: () => void
}
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined//这里使用断言是为了避免ts警告,由于不知道pet到底是何种类型,这里还是做了!==判断
}
function getPet(): Fish | Bird {
  if (Math.random() < 0.5) {
    return { swim: () => {} }
  }
  return { fly: () => {} }
}
let pet = getPet()
if (isFish(pet)) {
  pet.swim() //let pet: Fish
} else {
  pet.fly() //let pet: Bird
}

类型谓词采用paramsName is Type的形式定义,其中paramsName一定是参数名称。

判别联合

先抛出一个问题:如果形状是圆形,就计算圆形的面积。如果形状是正方形,就计算正方形的面积。
我们想到第一种类型定义方法:

interface Shape {
  kind: 'circle' | 'square'
  radius?: number
  sideLen?: number
}
function handleShape(shape: Shape) {
  if (shape.kind === 'circle') {
    return Math.PI * shape.radius ** 2 //“shape.radius”可能为“未定义”
  } else {
    return shape.sideLen ** 2 //“shape.sideLen”可能为“未定义”
  }
}

这个时候ts会报错,报错也是正常的,因为radius和sideLen都是可选值,你在传一个shape时,只有kind必传,有可能两个可选值都没传递,有可能kind是circle时你传递了sideLen,有可能kind是square时你传递了radius,这都会导致你无法计算面积,而ts也能推断这些。
我们可以把圆形和方形分开定义,但是给他们设置一个公共属性,让ts能够识别它属于什么形状。

interface Circle {
  kind: 'circle'
  radius: number
}
interface Square {
  kind: 'square'
  sideLen: number
}
type Shape = Circle | Square
function handleShape1(shape: Shape) {
  if (shape.kind === 'circle') {
    return Math.PI * shape.radius ** 2//(parameter) shape: Circle
  } else {
    return shape.sideLen ** 2//(parameter) shape: Square
  }
}
//也可以使用switch语句
function handleShape2(shape: Shape) {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2
    case 'square':
      return shape.sideLen ** 2
  }
}

never类型

never 类型表示永远不会出现的值的类型。通常情况下,never 类型用于表示抛出异常或者永远不会结束的函数的返回类型。例如,一个函数如果抛出异常,它的返回类型就可以定义为 never

穷举检查

never 类型可分配给每个类型;但是,没有类型可分配给 never(never 本身除外)
例如:switch语句中,当所有可能情况处理判断后,shape到了default分支,就可以将shape赋值给never类型的变量。

interface Circle {
  kind: 'circle'
  radius: number
}
interface Square {
  kind: 'square'
  sideLen: number
}
type Shape = Circle | Square
function handleShape(shape: Shape) {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2
    case 'square':
      return shape.sideLen ** 2
    default:
      const _exhaustiveCheck: never = shape
      return _exhaustiveCheck
  }
}

但是,ts不允许可能出现的情况,未经判断,就赋值给never类型的变量,下面的代码会报错。

interface Circle {
  kind: 'circle'
  radius: number
}
interface Square {
  kind: 'square'
  sideLen: number
}
interface Triangle {
  kind: 'triangle'
  sideLen: number
}
type Shape = Circle | Square | Triangle
function handleShape(shape: Shape) {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2
    case 'square':
      return shape.sideLen ** 2
    default:
      const _exhaustiveCheck: never = shape//error:不能将类型“Triangle”分配给类型“never”
      return _exhaustiveCheck
  }
}
  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值