[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)是一种通过共享的判别字段 来安全区分不同对象类型的模式。它能让编译器在 switchif 判断时自动缩小类型范围,并提供 穷尽性检查,避免遗漏分支。

它用来解决这种问题:

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;
  }
}
相关推荐
掰头战士8 小时前
多重影分身!恨不得把一个agent掰成两个? 还真能干!
typescript·llm·agent
shmily麻瓜小菜鸡12 小时前
TDD(测试驱动开发)详解
开发语言·javascript·typescript·node.js·ecmascript
雾隐隐o1 天前
TypeScript 基础:数据类型、接口与类型推论
typescript
flash俊杰1 天前
Zod Schema 驱动的 IPC 契约:从入参校验到状态机一致性的三层防御
javascript·typescript
愛芳芳2 天前
基于 Electron + Vue3 的仿 PC 微信客户端项目实战
前端·javascript·css·elementui·typescript·electron·vue
神秘的猪头2 天前
TypeScript 高级用法全解析:从泛型到 infer,把类型系统真正用起来
前端·typescript
FYKJ_20103 天前
【毕设分享】基于Web的校园兼职信息发布与申请系统07059
前端·vue.js·spring boot·mysql·typescript·spark·课程设计
ynchyong3 天前
TS 基础
typescript·ts
西瓜太郎4993 天前
别再手算 Token 成本:用 React 状态机做一个可追溯的模型费用计算器
react.js·typescript
右耳朵猫AI4 天前
Web前端周刊2026W37 | Shopify 转原生、React 编译 Rust 化、Vitest 5.0、Rslib 1.0
前端·javascript·react.js·typescript·node.js