← 返回目录

第 2.1 章:类型与运算

TypeScript 的类型系统是它最强大的特性

1. 基本类型

TypeScript 的基本类型与你熟悉的语言类似,但全部使用小写:

// 显式类型注解
let name: string = "TypeScript";
let age: number = 12;           // 没有 int/float 之分,统一为 number
let isDone: boolean = false;
let big: bigint = 100n;         // 大整数,注意后缀 n
let id: symbol = Symbol("id");  // 唯一标识符

💡 类型推断(Type Inference):TypeScript 能自动推断变量类型,不需要总是手写注解。

// 类型推断 —— 编译器自动推导类型
let city = "Shanghai";   // 推断为 string
let count = 42;          // 推断为 number
let active = true;       // 推断为 boolean

// const 声明会推断为字面量类型
const PI = 3.14;         // 推断为 3.14,不是 number
const lang = "TS";       // 推断为 "TS",不是 string

🔄 与 Java 的区别

TS 的类型是结构化的(structural typing),不是名义的(nominal typing)。两个结构相同的类型是兼容的,不需要显式声明 implements。这被称为"鸭子类型"——如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子。

2. 特殊类型

// any —— 逃生舱口,关闭类型检查(尽量避免!)
let anything: any = 42;
anything = "hello";     // 不报错
anything.foo.bar;       // 不报错,但运行时可能崩溃

// unknown —— 安全的 any,使用前必须检查类型
let data: unknown = fetchSomething();
// data.toString();     // ❌ 编译错误!不能直接使用
if (typeof data === "string") {
    data.toUpperCase(); // ✅ 类型收窄后可以使用
}

// void —— 函数无返回值
function log(msg: string): void {
    console.log(msg);
}

// null 和 undefined —— 两个独立的类型
let n: null = null;
let u: undefined = undefined;

// never —— 永远不会有值的类型
function throwError(msg: string): never {
    throw new Error(msg);  // 函数永远不会正常返回
}

function infiniteLoop(): never {
    while (true) {}        // 永远不会结束
}

💡 经验法则:用 unknown 替代 anyunknown 强制你在使用前进行类型检查,更安全。

3. 字面量类型与联合类型

字面量类型

TypeScript 允许将具体的值作为类型,这在其他语言中很少见:

let yes: true = true;
// yes = false;         // ❌ 类型 'false' 不能赋给类型 'true'

let httpOk: 200 = 200;
let greeting: "hello" = "hello";

联合类型(Union Types)

| 组合多个类型,表示"其中之一":

// 经典用法:限定有效值的范围
type Direction = "up" | "down" | "left" | "right";

function move(dir: Direction) {
    console.log(`Moving ${dir}`);
}

move("up");       // ✅
// move("forward"); // ❌ 类型 '"forward"' 不能赋给类型 'Direction'

// 混合类型联合
type ID = string | number;
let userId: ID = "abc123";
userId = 42;      // ✅ 也合法

可辨识联合(Discriminated Unions)

通过共有的"标签"字段区分联合中的不同类型,这是 TS 中极其常用的模式:

type Shape =
    | { kind: "circle"; radius: number }
    | { kind: "rectangle"; width: number; height: number };

function area(shape: Shape): number {
    switch (shape.kind) {
        case "circle":
            return Math.PI * shape.radius ** 2;
        case "rectangle":
            return shape.width * shape.height; // TS 在这里知道有 width 和 height
    }
}

4. 交叉类型

& 合并多个类型,结果类型包含所有属性:

interface HasName {
    name: string;
}

interface HasAge {
    age: number;
}

// 交叉类型:同时拥有 name 和 age
type Person = HasName & HasAge;

const alice: Person = {
    name: "Alice",
    age: 30,
};

// 实用场景:给已有类型添加字段
type Timestamped<T> = T & { createdAt: Date; updatedAt: Date };

type TimestampedPerson = Timestamped<Person>;
// 等价于 { name: string; age: number; createdAt: Date; updatedAt: Date }

💡 联合 | 是"或"——值是其中一种类型;交叉 & 是"且"——值同时满足所有类型。

5. 类型断言与非空断言

类型断言(Type Assertion)

当你比编译器更了解某个值的类型时,用 as 告诉编译器:

// DOM 操作是最常见的场景
const el = document.getElementById("app") as HTMLDivElement;
el.style.color = "red";  // 不会报错,因为你断言了它是 HTMLDivElement

// 也可以用尖括号语法(在 JSX/TSX 中不可用)
const el2 = <HTMLDivElement>document.getElementById("app");

// 双重断言 —— 在两个不相关的类型间转换(谨慎使用)
const value = "hello" as unknown as number;

非空断言(Non-null Assertion)

! 后缀告诉编译器"这个值一定不是 null/undefined":

// getElementById 返回 HTMLElement | null
const el = document.getElementById("app")!;  // 断言不为 null
el.style.color = "blue";

// 等价于
const el2 = document.getElementById("app");
if (el2 === null) throw new Error("Element not found");
el2.style.color = "blue";

⚠️ 类型断言和非空断言都会绕过类型检查。如果断言错误,运行时依然会崩溃。优先使用类型守卫进行安全的类型收窄。

6. 类型守卫

类型守卫让 TypeScript 在条件分支中自动收窄(narrow)类型,无需手动断言:

typeof 守卫

function padLeft(value: string | number): string {
    if (typeof value === "number") {
        return " ".repeat(value);   // 这里 value 是 number
    }
    return value;                   // 这里 value 是 string
}

instanceof 守卫

function formatDate(input: string | Date): string {
    if (input instanceof Date) {
        return input.toISOString();   // input 是 Date
    }
    return new Date(input).toISOString(); // input 是 string
}

in 守卫

interface Fish { swim(): void; }
interface Bird { fly(): void; }

function move(animal: Fish | Bird) {
    if ("swim" in animal) {
        animal.swim();   // animal 是 Fish
    } else {
        animal.fly();    // animal 是 Bird
    }
}

自定义类型守卫函数

// 返回类型 'x is Type' 是关键
function isString(value: unknown): value is string {
    return typeof value === "string";
}

function process(input: unknown) {
    if (isString(input)) {
        console.log(input.toUpperCase()); // TS 知道 input 是 string
    }
}

// 实际场景:验证 API 响应
interface ApiResponse {
    data: unknown;
    status: number;
}

interface User {
    id: number;
    name: string;
    email: string;
}

function isUser(obj: unknown): obj is User {
    return (
        typeof obj === "object" &&
        obj !== null &&
        "id" in obj &&
        "name" in obj &&
        "email" in obj
    );
}

7. TypeScript 特有运算符

这些运算符来自 JavaScript(ES2020+),但在 TypeScript 中结合类型系统尤其强大:

?? 空值合并(Nullish Coalescing)

// ?? 只在左侧为 null 或 undefined 时取右侧值
const port = config.port ?? 3000;

// 区别于 ||:|| 在左侧为任何 falsy 值时取右侧
const count1 = 0 || 10;     // 10  (0 是 falsy)
const count2 = 0 ?? 10;     // 0   (0 不是 null/undefined)

const name1 = "" || "默认";  // "默认"(空字符串是 falsy)
const name2 = "" ?? "默认";  // ""   (空字符串不是 null/undefined)

?. 可选链(Optional Chaining)

interface Company {
    name: string;
    address?: {
        city?: string;
        street?: string;
    };
}

function getCity(company: Company): string | undefined {
    // 无需层层判断 null —— 任何一步为 null/undefined 就返回 undefined
    return company.address?.city;
}

// 也适用于方法调用和数组索引
const result = obj.method?.();       // 方法可能不存在
const first = arr?.[0];              // 数组可能是 undefined
const value = map.get?.("key");      // get 可能不存在

=== vs ==(严格相等)

// === 严格相等:不做类型转换(推荐始终使用)
0 === ""       // false
0 === false    // false
null === undefined  // false

// == 宽松相等:会做类型转换(避免使用)
0 == ""        // true  😱
0 == false     // true  😱
null == undefined   // true

// TypeScript 在 strict 模式下对 == null 有特殊处理
// value == null 等价于 value === null || value === undefined
function example(value: string | null | undefined) {
    if (value == null) {
        // 这里 value 是 null | undefined
    }
}

🔄 与 Python 的区别

Python 的 type hints 是运行时忽略的(def foo(x: int) 传入字符串不会报错),而 TypeScript 的类型在编译时强制检查。TS 的 ?. 类似 Python 中没有的语法糖;Python 需要写 getattr(obj, 'attr', None) 或链式 if 判断。

📋 本章要点

类型推断很聪明

大多数情况下不需要手写类型注解,TS 会自动推断。在函数参数和复杂场景中添加注解即可。

结构化类型系统

TS 是鸭子类型——只看结构,不看名字。与 Java/C++ 的名义类型系统截然不同。

联合类型是核心

联合类型 | 配合可辨识联合和类型守卫,是 TS 中最常用的类型建模方式。

unknown 优于 any

需要"任意类型"时用 unknown,它要求你在使用前进行类型检查,避免运行时错误。

类型守卫 > 类型断言

优先使用 typeofinstanceofin 等类型守卫实现安全的类型收窄,而非 as 断言。

善用 ?. 和 ??

可选链 ?. 和空值合并 ?? 大幅简化 null/undefined 的处理,配合类型系统使用效果更佳。