Skip to content

类型收窄(条件逻辑的类型)

不要被名词吓到...就简单的经验概念

指 ts静态分析 自动推导未来运行时条件语句中的类型

本属于原理理论,但因为影响一些常用逻辑编写类型,因此要熟悉

if 语句 typeof instanceof

ts 内部需要识别 js 条件逻辑中的 typeof/instanceof , ts 称这种识别为 类型保护 (type guard)

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

ts 保留 js typeof 语法的遗留问题(typeof null === 'object')

if 语句 隐式类型转换

js 的 条件语句(if&&||switch) 存在隐式类型转换

0 、NaN、""、0n、null undefined 这些值都会被转为 false,其他的值则会被转为 true

👆 同一个例子,js 的隐式类型转换,ts也可以识别,成功收窄类型

控制流分析(Control flow analysis)

paddingnumber 类型,无法达到 (unreachable) 的部分,就会将 number类型从 number | string 类型中删除掉

这种基于可达性(reachability) 的代码分析就叫做控制流分析(control flow analysis)。

上面的都是ts内部自动推导的 类型收窄

下面是需要手动实现的 类型收窄

is type 手动类型收窄

ts
function isString(s: any) {
  return typeof s === 'string';
}

function toUpperCase(x: unknown) {
  if(isString(x)) {
    x.toUpperCase(); // ⚡️ x is still of type unknown
  }
}

if 条件语句里 ts 不能自动执行函数 (有了 ts 后一般也很少要运行时判断类型了吧)

👇 使用 is + type 定义函数的返回

ts
function isString(s: any): s is string { // <-- ✨ 入参 is type
  return typeof s === 'string';
}

✨ 过滤数据时有用!!!

filter 等数组工具函数 callback 是返回 true/false 的函数, 在函数式编程里常抽离 原子函数

把这个原子函数定义成is,可以用于更多地方

ts
interface Fish {}
interface Bird {}

function isFish(item: any): item is Fish {} // <-- ✨ 原子函数

const zoo: (Fish | Bird)[] = [fish1, fish2, bird1]
const afterList: Fish[] = zoo.filter(isFish)

可辨别联合(Discriminated unions)

👇 我们常遇到这样的数据结构, 不同的 itemType 走不同的逻辑

aParamsbParams 都是非必填的

ts 并不知道这种数据内部逻辑

👇 可以用非空断言解决(用 as 同理)

👇 但是我们希望类型声明可以更语义化,从 类型 上看出数据的内部逻辑

ts
interface A {
  itemType: 'a',
  aParams: number
}

interface B {
  itemType: 'b',
  bParams: number
}

type AorB = A | B // <-- ✨ 可辨别联合(Discriminated unions)

function run(params: AorB) {
  if(params.itemType === 'a') {
    return params.aParams * 2 // ✅
  }else{
    return params.bParams * 2 // ✅
  }
}

可辨别联合的应用远不止这些,比如

  • 消息模式
  • 客户端服务端的交互
  • 状态管理框架

试想在消息模式中,我们会监听和发送不同的事件,这些都是以名字进行区分,不同的事件还会携带不同的数据,这就应用到了可辨别联合

js
// 消息 发布订阅
const info = {
  typeName: 'doA',
  params: {}
}

// 控制中心
function handle(info) {
  if(info.typeName === 'doA') {
    doFnA(info.params) // <-- 💥 ts 声明不了逻辑相关数据的类型 只能定义成 any
  }
  // ...
}