WinddSnow

TypeScript-Basic-Types-and-Type-Annotations

字数统计: 3.2k阅读时长: 12 min
2026/07/30

第64课:基本类型与类型注解——原始类型、数组、元组、any/unknown/never/void

TypeScript 的核心价值在于静态类型检查:在代码运行之前发现潜在的类型错误。类型注解(Type Annotation)是 TypeScript 的基础语法,它为变量、函数参数和返回值赋予明确的类型约束。本节课将系统讲解 TypeScript 的原始类型、数组与元组声明,以及四个具有特殊语义的顶级类型——anyunknownnevervoid。理解这些类型的适用场景与限制,是构建类型安全的 TypeScript 程序的第一步。


1. 原始类型(Primitive Types)

TypeScript 的原始类型与 JavaScript 运行时类型一一对应,使用小写形式声明。

1
2
3
4
5
6
7
let isDone: boolean = true;
let count: number = 42;
let username: string = 'Alice';
let bigNumber: bigint = 100n;
let uniqueId: symbol = Symbol('id');
let nothing: null = null;
let notAssigned: undefined = undefined;
TypeScript 类型 对应 JavaScript 类型 示例值 说明
boolean Boolean truefalse 逻辑值。
number Number 423.14NaNInfinity 所有数字(整数、浮点数、特殊值)。
string String 'hello'"world"、模板字符串 文本数据。
bigint BigInt 100n 超大整数,目标须 ≥ ES2020。
symbol Symbol Symbol('key') 全局唯一标识。
null Null null 表示空值。
undefined Undefined undefined 表示未定义。

1.1 类型推断

TypeScript 能根据初始值自动推断类型,此时不需要显式注解。仅在无法推断(如声明未赋值)或需要明确约束时添加注解。

1
2
3
4
5
let inferredString = 'hello'; // 推断为 string
// inferredString = 42; // ❌ 类型错误

let declaredLater: string; // 无初始值,必须注解
declaredLater = 'world';

最佳实践优先让 TypeScript 推断类型,仅在函数参数、未赋值的变量、复杂对象和需要明确约束的公共 API 上添加类型注解。


2. 数组与元组

2.1 数组类型

声明数组类型有两种等价语法:

1
2
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ['a', 'b', 'c']; // 泛型写法

推荐使用 T[] 写法,因其更简洁且与 JSX 兼容(<T>.tsx 文件中可能被误认为标签)。

只读数组:使用 readonly 修饰符防止修改。

1
2
let readonlyArr: readonly number[] = [1, 2, 3];
// readonlyArr.push(4); // ❌ 类型错误:readonly 数组不可修改

2.2 元组(Tuple)

元组是一种固定长度、各位置类型已知的数组。适用于表示固定结构的数据(如 CSV 行、坐标对)。

1
2
3
4
5
6
let point: [number, number] = [10, 20];
let user: [string, number, boolean] = ['Alice', 25, true];

// 元组超出长度访问时类型为联合类型
console.log(user[0].toUpperCase()); // ✅ 明确为 string
// user[1].toUpperCase(); // ❌ 类型错误:number 没有 toUpperCase

元组的只读版本readonly [number, number] 拒绝任何修改。

具名元组元素(TypeScript 4.0+):为元组每个位置添加标签,增强可读性。

1
let httpResponse: [status: number, body: string] = [200, 'OK'];

2.3 数组与元组的区别

特性 数组 T[] 元组 [T1, T2, ...]
长度 可变 固定(声明时确定)
各元素类型 统一 每个位置类型可不同
越界访问 返回 T 类型 返回各位置类型的联合类型
典型场景 同质数据集合 异构固定结构(CSV、坐标、API 返回)

3. 顶级类型:anyunknownnevervoid

这四个类型在 TypeScript 类型系统中扮演特殊角色:它们是“容器型”类型,用于表示未知、不可达、无值等极端情况。

3.1 any:逃生舱(禁用检查)

any 类型关闭该变量的所有类型检查,可以赋值为任何值、调用任何方法、赋值给任何类型。它是将 JavaScript 代码快速迁移到 TypeScript 时的“临时方案”,但应尽量避免在生产代码中使用

1
2
3
let loose: any = 42;
loose = 'hello';
loose.nonExistentMethod(); // 运行时错误,但 TypeScript 不会提示

any 的危险性:它会导致类型污染。any 类型的值在赋值给其他变量时,会“传染”对方。

1
2
let dangerous: any = 'could be anything';
let safeString: string = dangerous; // ❗ 无错误——类型安全被打破

使用建议:仅在极少数无法确定类型的场景(如解析动态 JSON、迁移遗留代码)使用 any。优先考虑 unknown

3.2 unknown:类型安全的 any

unknown 表示“我们不知道这个值的类型”。与 any 的区别在于:不能直接对 unknown 值执行任何操作,除非通过类型守卫(Type Guard) 缩窄其类型。

1
2
3
4
5
6
7
8
let userInput: unknown = 'hello';

// userInput.toUpperCase(); // ❌ 错误:unknown 不可直接操作

// 必须通过类型守卫收窄
if (typeof userInput === 'string') {
console.log(userInput.toUpperCase()); // ✅ 此分支内 userInput 被收窄为 string
}
特性 any unknown
赋值给其他类型 ✅ 允许(破坏类型安全) ❌ 禁止(需收窄后赋值)
直接调用方法/属性 ✅ 允许(可能运行时崩溃) ❌ 禁止(编译时错误)
适用场景 快速迁移、动态数据(不推荐) 安全处理未知外部数据(API 响应、用户输入)

最佳实践:当确实无法确定类型时,使用 unknown 而非 any,并通过类型守卫安全地使用它。

3.3 void:无返回值

void 表示函数的返回值不应该被使用。它是 undefined 的超类型——undefined 可以赋值给 void,但反之不行。

1
2
3
4
function logMessage(msg: string): void {
console.log(msg);
// 无 return 语句
}

注意:在 JavaScript 中,无 return 的函数实际返回 undefined。TypeScript 中,void 类型的变量只能接受 undefined(开启 strictNullChecks 时)或 null(关闭时)。

1
2
let unusable: void = undefined; // ✅
// let unusable2: void = null; // ❌ strictNullChecks 下

3.4 never:永不存在的值

never 表示永远不会到达永远不会产生值的类型。通常出现在:

  • 永远抛出异常的函数(函数执行中断,无返回值)。
  • 死循环函数(函数永不结束)。
  • 类型守卫中不可能到达的分支(用于“穷尽性检查”)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 抛出异常的函数
function throwError(msg: string): never {
throw new Error(msg);
}

// 死循环
function infiniteLoop(): never {
while (true) {}
}

// 穷尽性检查(switch default)
type Shape = 'circle' | 'square';
function area(shape: Shape): number {
switch (shape) {
case 'circle': return Math.PI * 1 * 1;
case 'square': return 1 * 1;
default:
const exhaustive: never = shape; // 如果 Shape 新增了类型,编译报错
return exhaustive;
}
}

nevervoid 的区别void 表示函数没有有意义的返回值(但仍然返回 undefined)。never 表示函数根本没有返回值(抛出异常或永不结束)。never 是所有类型的子类型——它可以赋值给任何类型,但没有任何类型(包括 any)可以赋值给 never


4. 类型注解与类型推断的平衡

在 TypeScript 中,并不是每个变量都需要显式注解。合理划分推断与注解的边界,既能保持代码简洁,又能确保类型安全。

场景 推荐做法 理由
变量声明时有初始值 推断(不加注解) 类型明确,多余注解增加维护负担。
函数参数 必须注解 TypeScript 无法推断参数类型,需明确契约。
函数返回值 通常推断 简单函数推断准确;复杂函数或公共 API 推荐显式注解以约束意图。
对象解构、复杂嵌套结构 注解或接口 明确结构,提升可读性和智能提示。
空数组 [] 必须注解 无法推断元素类型,注解如 const arr: string[] = []

课后练习

一、概念自测(选择题 / 填空题)

  1. (单选) 以下关于 unknown 类型的描述,正确的是?
    A. unknown 类型的变量可以调用任意方法。
    B. unknown 是类型安全的“任意类型”,使用前必须进行类型缩窄。
    C. unknownany 行为完全相同。
    D. unknown 类型变量不能赋值给其他变量。

  2. (单选) 一个函数体只有 while (true) {},其返回值类型应该是?
    A. void
    B. undefined
    C. never
    D. any

  3. (填空) 声明一个包含两个元素的元组,第一个是字符串,第二个是数字,语法为:let pair: ______ = ['age', 25]

  4. (多选) 以下哪些类型属于 TypeScript 的原始类型?
    A. string
    B. number
    C. object
    D. boolean

二、AI 编程任务:编写面向 AI 的提示词

场景:你需要编写一个 TypeScript 工具函数 safeGet(obj, path, fallback),它安全地从一个嵌套对象中按路径取值。要求如下:

  • 第一个参数 objunknown 类型(表示不确定的外部数据)。
  • 第二个参数 path 为点分隔的字符串(如 'user.address.city')。
  • 第三个参数 fallback 为任意类型的默认值。
  • 函数内部使用类型守卫逐步缩窄类型(先判断 obj 是否为对象,再按路径逐层访问)。
  • 如果路径中任何一层不存在或无法访问,返回 fallback 值。
  • 使用 const 声明函数,添加完整的 JSDoc 注释(包括参数类型、返回值和示例)。

任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的 TypeScript 代码。提示词中需明确指定 unknown 的类型缩窄方式、路径解析逻辑和异常处理。

三、Agent 模式下的提示词示例

你是一个资深前端开发 Agent。请编写一个 TypeScript 工具函数 safeGet,安全地从嵌套对象中按路径取值。需要创建文件 src/safeGet.ts

  • 函数签名:function safeGet(obj: unknown, path: string, fallback: unknown): unknown
  • 先判断 obj 是否为对象且非 null(typeof obj === 'object' && obj !== null),否则直接返回 fallback
  • path. 分割为字符串数组,遍历每个 key:检查当前值是否为对象且包含该 key,若是则继续深入,否则返回 fallback
  • 遍历结束后返回最终值。
  • 添加 JSDoc 注释,包含 @param@returns@example
  • 在文件末尾添加一个简短的测试代码(使用 console.log 验证):
    • safeGet({ user: { name: 'Alice' } }, 'user.name', 'Unknown') 返回 'Alice'
    • safeGet(null, 'a.b', 'fallback') 返回 'fallback'
    • safeGet({}, 'a.b', 'fallback') 返回 'fallback'
  • 使用 export 导出函数,代码遵循 TypeScript 严格模式。完成后列出完整文件内容。

四、面试真题与参考答案

题目(美团前端面试题):

请详细解释 TypeScript 中 anyunknownnever 三个类型的区别,并分别给出一个合适的使用场景。

参考答案

类型 含义 赋值行为 操作行为 典型场景
any 任意类型,完全放弃类型检查。 可赋值给任何变量,也可接受任何值。 可调用任何方法、访问任何属性。 JavaScript → TypeScript 迁移时的临时过渡(不推荐长期使用)。
unknown 未知类型,类型安全的“任意类型”。 只能赋值给 unknownany 类型。 不能直接操作,必须通过类型守卫缩窄。 处理外部 API 响应、用户输入等不确定的数据。
never 永不存在的值,表示不可达的状态。 可赋值给任何类型(底部类型)。 没有任何值可以赋给 never 函数中抛出异常或死循环;用于穷尽性检查(switch default)。

补充说明

  • any危险的,因为它完全绕过了类型系统,会导致潜在的运行时错误。应该用 unknown 替代 any 进行安全的动态数据处理。
  • unknown 迫使开发者在操作数据前通过 typeofinstanceof 或自定义类型守卫验证数据的结构,从而在编译阶段捕获潜在错误。
  • never 在类型体操和高级类型编程中扮演关键角色(如条件类型、映射类型过滤),也用于确保所有可能的情况都被处理(穷尽性检查)。

课后练习答案

一、概念自测答案

  1. B

    • 解析:unknown 是类型安全的顶层类型,操作前必须缩窄。A 错误(不可直接调用方法);C 错误(行为不同);D 错误(缩窄后可赋值)。
  2. C

    • 解析:死循环函数永远不会返回,返回值类型为 never
  3. [string, number]

    • 解析:元组语法 [string, number] 定义各位置的类型。
  4. A、B、D

    • 解析:stringnumberboolean 均为原始类型。object 是非原始类型。

二、AI 编程任务参考答案(提示词示例)

示例提示词
“请编写一个 TypeScript 函数 safeGet(obj: unknown, path: string, fallback: unknown): unknown,安全地从嵌套对象中按路径取值。要求:

  • 先判断 obj 是否为对象且非 null(typeof obj === 'object' && obj !== null),否则直接返回 fallback
  • path. 分割,使用 for...of 遍历 key,每次检查 current 是否为对象且 key in current,若是则 current = current[key],否则返回 fallback
  • 遍历结束后返回 current
  • 使用 const 声明函数,添加 JSDoc 注释(@param、@returns、@example)。
  • 提供三个测试用例的注释。直接输出完整代码。”
CATALOG
  1. 1. 第64课:基本类型与类型注解——原始类型、数组、元组、any/unknown/never/void
    1. 1.1. 1. 原始类型(Primitive Types)
      1. 1.1.1. 1.1 类型推断
    2. 1.2. 2. 数组与元组
      1. 1.2.1. 2.1 数组类型
      2. 1.2.2. 2.2 元组(Tuple)
      3. 1.2.3. 2.3 数组与元组的区别
    3. 1.3. 3. 顶级类型:any、unknown、never、void
      1. 1.3.1. 3.1 any:逃生舱(禁用检查)
      2. 1.3.2. 3.2 unknown:类型安全的 any
      3. 1.3.3. 3.3 void:无返回值
      4. 1.3.4. 3.4 never:永不存在的值
    4. 1.4. 4. 类型注解与类型推断的平衡
    5. 1.5. 课后练习
      1. 1.5.1. 一、概念自测(选择题 / 填空题)
      2. 1.5.2. 二、AI 编程任务:编写面向 AI 的提示词
      3. 1.5.3. 三、Agent 模式下的提示词示例
      4. 1.5.4. 四、面试真题与参考答案
    6. 1.6. 课后练习答案
      1. 1.6.1. 一、概念自测答案
      2. 1.6.2. 二、AI 编程任务参考答案(提示词示例)