[TypeScript学习笔记-04]频繁使用的各种Type Narrowing手段

TypeScript 的类型系统是一种静态契约机制,它定义了值在编译期必须满足的结构和行为约束。类型可以描述对象的成员(名称、类型、可选性、可写性)、函数签名、字面量取值范围等。联合类型 A | B 表示一个值可能是 A 或 B,从集合论角度看是类型集合的并集,但它不会把成员结构合并,而是保持"二选一"的语义。所以当我们将一个变量声明为联合类型的时候,在默认的情况下只有针对交集 数据成员的访问是安全的。为了不仅限于访问联合类型的交集成员,但是并集成员范围又太宽,在编程的时候我们需要引入响应的条件判断,缩小成员的范围,使针对某些API的访问控制在一个安全的边界之内,我们将其称为类型收窄(Narrowing)。

1. typeof操作符

JavaScript 支持 typeof 运算符,它可以提供运行时值类型的基本信息。TypeScript 期望它返回一组特定的字符串:

  • "string"
  • "number"
  • "bigint"
  • "boolean"
  • "symbol"
  • "undefined"
  • "object"
  • "function"

注 : typeof null === "object" 是成立的,所以下面代码中的strs可能为null,具体类型为string[]|null:

typescript 复制代码
function printAll(strs: string | string[] | null) {
  if (typeof strs === "object") {
    for (const s of strs) {
       //'strs' is possibly 'null'. strs的类型为 string[]
      console.log(s);
    }
  } else if (typeof strs === "string") {
    console.log(strs);
  } 
}

2. 基于真值类型的条件判断

在 JavaScript 中,我们可以在条件语句、&&、||、if 语句、布尔否定(!)等中使用任何表达式 ,并不要求其条件必须是布尔类型。在 JavaScript 中,类似 if 这样的语句首先会将条件强制为布尔值以使其有意义,然后根据结果是真还是假来选择执行分支。

  • 0
  • NaN
  • "" (the empty string)
  • 0n (the bigint version of zero)
  • null
  • undefined

所以上面的问题可以通过 && 操作符来确保strs不可能为null。

typescript 复制代码
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);
  }
}

3. 对等判断

TypeScript 还使用 switch 语句和相等性检查(例如 ===、!==、== 和 !=)来缩小类型范围。

typescript 复制代码
function example(x: string | number, y: string | boolean) {
  if (x === y) { // 既然对等,必然是字符串
    x.toUpperCase();          
    y.toLowerCase();          
  } else {
    console.log(x); //(parameter) x: string | number
    console.log(y); //(parameter) y: string | boolean
  }
}

注 : == /!= 和===/!==的区别:

typescript 复制代码
interface Container {
  value: number | null | undefined;
}
 
function multiplyValue(container: Container, factor: number) {
  // 排除null和undefined.
  if (container.value != null) {
    console.log(container.value);                           
    // (property) Container.value: number 
    container.value *= factor;
  }
}

4. in 操作符

运算符 in用于判断一个对象或其原​​型链 是否具有名为 name 的属性。TypeScript 会利用这一点来缩小潜在类型的范围:

typescript 复制代码
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
  }
}

注 :对于可缺省数据成员,可能存在,也可能不存在。

5. instanceof 操作符

运算符 instanceof 可以检查一个值是否是另一个值的实例 。更具体地说,在 JavaScript 中,x instanceof Foo 检查 x 的原型链 是否包含 Foo.prototype。

typescript 复制代码
function logValue(x: Date | string) {
  if (x instanceof Date) {
    console.log(x.toUTCString()); // (parameter) x: Date
  } else {
    console.log(x.toUpperCase()); // (parameter) x: string
  }
}

6. 赋值语句

当我们给任何变量赋值时,TypeScript 会查看赋值语句的右侧,并相应地缩小左侧的范围。如下的代码是合法的:

typescript 复制代码
let x = Math.random() < 0.5 ? 10 : "hello world!"; //let x: string | number

x = 1; 
console.log(x); //let x: number

x = "goodbye!"; 
console.log(x);  //let x: string

如下则是有问题的:

typescript 复制代码
let x = Math.random() < 0.5 ? 10 : "hello world!"; //let x: string | number

x = 1;
console.log(x);  //let x: number

x = true; // 类型不匹配,赋值失败.
 
console.log(x); // let x: string | number

7. 控制流分析

TypeScript会自动分析控制流,并在相应分支智能实施类型收窄。

typescript 复制代码
function padLeft(padding: number | string, input: string) {
  if (typeof padding === "number") {
    return " ".repeat(padding) + input;
  }
  return padding + input; // 排除number,只能是string
}

8. 类型断言

要定义用户自定义type guards,我们只需要定义一个返回类型为类型谓词的函数:

typescript 复制代码
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

let pet = getSmallPet(); 
if (isFish(pet)) {
  pet.swim();
} else {
  pet.fly();
}

注 :TypeScript函数的两种返回形式:基于确定类型的值 + 类型谓词

9. assert 函数

assert 函数可以在调用之后根据指定的断言强行实施类型收窄。

typescript 复制代码
function multiply(x, y) {
  assert(typeof x === "number");
  assert(typeof y === "number");
  return x * y;
}

10. 可区分的联合(Discriminated Unions)

在 TypeScript 中,可区分联合类型(Discriminated Unions)是一种通过共享的判别字段 来安全区分不同对象类型的模式。它能让编译器在 switch 或 if 判断时自动缩小类型范围,并提供 穷尽性检查,避免遗漏分支。

它用来解决这种问题:

typescript 复制代码
interface Shape {
  kind: "circle" | "square";
  radius?: number;
  sideLength?: number;
}

function getArea(shape: Shape) {
  return shape.kind == "circle" 
    ? Math.PI * shape.radius ** 2 //'shape.radius' 可能是'undefined'.
    : shape.sideLength ** 2 ;     //'shape.sideLength' 可能是'undefined'.
}

解决方案:

typescript 复制代码
interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape): number {
  return shape.kind == "circle" 
    ? Math.PI * shape.radius ** 2 
    : shape.sideLength ** 2 ; 
}

11. 穷尽性检查(Exhaustiveness checking)

缩小范围时,你可以将联合类型的选项减少到排除所有可能性的程度 。在这种情况下,TypeScript 会使用 never 类型来表示不应该存在的状态。never 类型可以赋值给任何类型;然而,除了 never 本身之外,没有任何类型可以赋值给 never。这意味着你可以使用类型缩小,并依靠 never 的出现来进行 switch 语句中的穷举检查。

typescript 复制代码
type Shape = Circle | Square;
 
function getArea(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.sideLength ** 2;
    default:
      const _exhaustiveCheck: never = shape;
      return _exhaustiveCheck;
  }
}
相关推荐
nyaomaru5 小时前
将一个真实的 TypeScript OSS 库从 tsup 迁移到 tsdown
前端·typescript
wflynn1 天前
GitHub 今日推荐|ts-rust:把微软 TypeScript-Go 编译器逐行移植成 Rust 实现
rust·typescript·开源·github·compiler·tsc
Duang007_1 天前
生产可观测性:从“系统慢“到“根因“的完整链路(Go / TypeScript)
后端·python·golang·typescript·prometheus
IllegalCreed2 天前
用 TypeScript 从零重写《仙剑奇侠传》:能通关的浏览器版,和一个给它做内容的可视化编辑器
typescript
_zxd2 天前
TypeScript 类型树
前端·typescript
Flynt2 天前
SvelteKit 3 升级实录:sv migrate 改完 6 个源文件,还给我留了张任务清单
typescript·前端框架·svelte
光影少年4 天前
Taro 是如何解析入口配置 app.config.ts 和页面配置的?
微信小程序·小程序·typescript·reactjs·百度小程序·taro
AI情绪识别开源5 天前
HFpEF 心房-心室耦联效率无创监测方向(AVCEI)
typescript·laravel·perl
凤城老人5 天前
从 PyQt6 到 Electron:给 Edge TTS 做一个“多角色配音机“的踩坑手记
javascript·typescript·electron
zhousenshan6 天前
TypeScript 入门到精通
typescript