Skip to content

TypeScript学习【一】初入 TypeScript

轩辕十四
Published date:

对于已经有静态类型语言经验的开发者来说,学习 TypeScript 的难点通常不在基础语法,而在于类型系统视角的切换。真正决定上手速度的,往往不是记住多少语法点,而是有没有先建立起正确的理解框架。

这一篇文章只讨论初入 TypeScript 时最容易影响后续理解的知识:

Table of contents

Open Table of contents

开始之前,先建立 4 个核心认知

1. TypeScript 是结构化类型,不是名义类型

很多传统静态类型语言更偏向名义类型系统。通常情况下,是否兼容,会先看它是不是同一个类型、是否遵守某个接口或协议、是否存在明确的声明关系。

TypeScript 默认更看重结构。如果两个对象的 shape 一样,它们就可能被视为兼容。

例如:

interface Pet {
  name: string;
}

class Dog {
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

const pet: Pet = new Dog("Lucky");

这里 Dog 并没有显式 implements Pet,但依然可以赋值成功。原因不在于类型名,而在于:

Dog 的实例结构满足 Pet 的要求。

这会直接影响后续对以下内容的理解:

2. TypeScript 不需要处处用 class

在很多静态类型语言里,class 或 struct 往往是代码组织的天然中心;但在 JavaScript / TypeScript 里,函数和对象本身就是更自然的组织单位。

因此,TypeScript 并不要求把行为和数据都塞进 class 里。很多情况下,更自然的表达方式是:

也就是说:

class 在 TypeScript 里只是可选工具,不是默认世界观。

这也是为什么 TypeScript 官方文档会强调:shape 往往比类型名更重要。

3. Union + Narrowing 是核心建模手段

很多静态类型语言在做状态建模时会自然想到:

而在 TypeScript 里,大量实际建模会落在:

例如下面这种状态建模:

type LoadState =
  | { kind: "idle" }
  | { kind: "loading" }
  | { kind: "success"; data: string }
  | { kind: "failure"; error: Error };

这类写法在用途上,部分承担了带关联值枚举的角色。原因在于它也能表达:

但要注意,两者并不等价:

所以更准确的理解是:

在很多状态建模场景里,静态类型语言常用带关联值的枚举,TypeScript 更常用 discriminated union。

4. 类型只存在编译期,运行时会被擦除

TypeScript 的类型系统服务于:

运行时仍然是 JavaScript。也就是说:

这一点会影响对以下问题的理解:

初入 TypeScript 最先要理解的 6 个知识块

1. 先完成类型系统心智校准

这一章最适合作为第一站。虽然标题面向 Java/C#,但对于有静态类型语言背景的开发者同样很有价值。原因在于:

这一章重点不是语法,而是心智模型。阅读时重点关注:

这一章的作用不是“学会写代码”,而是完成心智校准。

2. 先理解编译器和严格模式在做什么

这一章内容本身不难,但不要跳过。它决定的是:

对有静态类型语言经验的开发者来说,这一章真正重要的不是语法,而是:

TypeScript 的编译器和严格模式是如何参与开发流程的。

3. 日常类型表达是进入代码阅读的起点

这一章是进入日常 TypeScript 代码的第一核心章节。重点内容包括:

其中最值得花时间的是:

需要特别建立下面几个对应关系:

4. 类型缩小决定了 TypeScript 是否真正好用

这一章是 TypeScript 真正开始“好用”的关键章节。重点包括:

这一章最重要的认识是:

TypeScript 的强项不只是“声明类型”,而是“根据运行时代码路径自动缩小类型范围”。

这和很多静态类型语言里的条件分支收窄体验有相通之处,但 TypeScript 更依赖 JavaScript 的运行时语义和控制流分析。

5. 对象类型是最常见的建模落点

TypeScript 里大量建模最终都会落在 object type 上。重点包括:

这一章的重点在于建立一个认识:

很多在静态类型语言里会被建成 struct、接口、class 的东西,在 TypeScript 里可能直接就是 object type。

其中最容易困惑的是 index signatures

关于 [index: number]: string 这个例子

官方文档经常用下面这个例子解释索引签名:

interface StringArray {
  [index: number]: string;
}

这个例子的重点不是“推荐这样声明数组”,而是在演示:

当一个对象支持用数字索引访问时,索引访问后的结果类型是什么。

如果它本来就是数组,那么实际开发里直接写:

const arr: string[] = ["a", "b", "c"];

通常更自然。

索引签名真正有价值的场景是:

例如配置对象:

interface SquareConfig {
  color?: string;
  width?: number;
  [propName: string]: unknown;
}

这里的含义是:

这类写法更适合表达“配置对象允许扩展字段”这种场景,而不是替代数组。

6. 泛型的重点是约束和推导,不只是语法

这一章对有泛型经验的开发者通常并不陌生,但需要重点关注的不是“什么是泛型”,而是:

TypeScript 泛型如何和结构化类型系统结合使用。

重点包括:

阅读时建议优先关注:

不要只把注意力放在 generic class 上,因为 TypeScript 里函数泛型的存在感通常更强。

理解这些知识时需要注意的几个点

初入 TypeScript 时,更适合用下面的方式理解这些内容:

1. 先校准认知,再看语法细节

第一章的任务是校准心智模型,不是快速记忆语法。

2. 看到 union 和 narrowing 时,多写小例子

这部分是最值得自己敲代码验证的内容。

3. 把 object type 当成高频建模工具

不要总是先找 class 或 protocol 的对应物,而要先问:

4. 对编译期和运行期保持清晰区分

看到类型系统时,要始终区分:

本篇结论

对于有静态类型语言经验的开发者来说,初入 TypeScript 时真正需要完成的,不是语法记忆,而是以下四个转换:

  1. 从名义类型转向结构化类型
  2. 从 class / protocol 导向,转向 union + object type + narrowing 导向
  3. 从“类型会在运行时存在”转向“类型只存在于编译期”
  4. 从“先学很多特性”转向“先建立最常用的类型建模能力”

只要完成这些转换,后面的函数类型、模块系统、Utility Types、Mapped Types、Conditional Types 才会真正进入“高收益理解”阶段。

Previous
C 语言重拾【九】联合类型 union
Next
Prompt学习六:为什么写完 Prompt 还不算结束