编码 · 编程语言
TypeScript 入门教程(零基础)
简短回答
TypeScript 就是带静态类型的 JavaScript。你添加 let count: number = 0 这样的类型注解,编译器 tsc 会先检查它们,再把代码转换为可在任何浏览器或 Node.js 中运行的普通 JavaScript。核心概念有:基本类型、描述对象形状的接口与类型别名、带类型收窄的联合类型,以及用于复用代码的泛型。类型在运行时会被擦除。
TypeScript 是什么,用来做什么
TypeScript 是微软开发的开源语言,2012 年首次发布。每个合法的 JavaScript 程序同时也是合法的 TypeScript 程序,因此可以逐个文件地引入。类型检查器会在你输入时发现属性拼错、漏掉 null 检查等错误,编辑器也会利用类型提供自动补全和安全重命名。它广泛用于大型前端应用(Angular 就是用它编写的,React、Vue 和 Svelte 也都支持良好)、Node.js 后端和各类库。
安装并运行 Hello, world
先安装 Node.js,然后在项目中用 npm 安装编译器。tsc 会把 .ts 文件编译成 .js。
const message: string = "Hello, world";
console.log(message);npm install --save-dev typescript
npx tsc --init # creates tsconfig.json
npx tsc hello.ts # writes hello.js
node hello.js
# Hello, world较新版本的 Node.js、Deno 和 Bun 也能直接运行 .ts 文件,做法是去掉类型注解。它们不做类型检查,所以仍需运行 npx tsc --noEmit 来查找类型错误。
基本类型与类型推断
你很少需要把每个类型都写出来。初始化变量时,TypeScript 会推断其类型:let n = 5 是 number。请为函数参数和公开 API 添加类型注解。
| 类型 | 示例 | 说明 |
|---|---|---|
number | let n: number = 3.5 | 整数和浮点数 |
string | let s: string = "hi" | 文本 |
boolean | let ok: boolean = true | 真或假 |
| 数组 | number[] 或 Array<number> | 两种写法含义相同 |
| 元组 | [string, number] | 长度和各位置类型固定 |
unknown | let x: unknown | 可以是任意值,使用前必须先检查 |
any | let y: any | 关闭类型检查,应避免使用 |
void / never | 函数返回类型 | 不返回任何值 / 永不返回 |
let count = 0; // inferred as number
count = "ten"; // error: Type 'string' is not assignable to type 'number'
const names: string[] = ["Ann", "Bo"];
const pair: [string, number] = ["age", 31];第二行是故意写错的编译错误,用来展示检查器会报告什么。
函数、流程控制与类型收窄
流程控制(if、for、while、switch)与 JavaScript 完全相同。TypeScript 增加的是类型收窄:在 typeof x === "string" 这样的检查之后,编译器会知道该分支内更具体的类型。
function greet(name: string, greeting = "Hello"): string {
return `${greeting}, ${name}!`;
}
function formatId(id: string | number): string {
if (typeof id === "number") {
return id.toFixed(0).padStart(6, "0"); // id is number here
}
return id.toUpperCase(); // id is string here
}
console.log(greet("Lin")); // Hello, Lin!
console.log(formatId(42)); // 000042
console.log(formatId("ab7")); // AB7string | number 是联合类型:值是所列类型之一。可选参数写作 name?: string,其类型包含 undefined。
接口与类型别名
用 interface 或 type 别名描述对象的形状。TypeScript 检查的是结构而不是名称:只要对象具有正确的属性就符合要求。
interface User {
id: number;
name: string;
email?: string; // optional
readonly createdAt: Date;
}
type Role = "admin" | "editor" | "viewer"; // string literal union
function describe(user: User, role: Role): string {
const contact = user.email ?? "no email";
return `${user.name} (${role}, ${contact})`;
}
const ann: User = { id: 1, name: "Ann", createdAt: new Date() };
console.log(describe(ann, "editor")); // Ann (editor, no email)标志性特性:泛型与可辨识联合
泛型让一个函数或类型适用于多种类型,同时保持它们之间的关联。可辨识联合是一组共享某个字面量字段的对象类型的联合,因此对该字段做 switch 就能收窄类型,编译器还能检查是否处理了所有情况。
function first<T>(items: T[]): T | undefined {
return items[0];
}
const n = first([10, 20]); // n: number | undefined
const s = first(["a", "b"]); // s: string | undefined
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rect":
return shape.width * shape.height;
}
}
console.log(n, s, area({ kind: "rect", width: 2, height: 3 })); // 10 a 6内置的工具类型可以由旧类型构造新类型:Partial<User> 让每个属性都变为可选,Pick<User, "id" | "name"> 只保留部分属性,Record<string, number> 则描述一个字典。
错误处理
错误的处理方式与 JavaScript 相同,使用 throw 和 try/catch。开启 strict 选项后,捕获到的值类型是 unknown,因为任何值都可能被抛出,所以使用前要先检查。
function parseAge(text: string): number {
const age = Number(text);
if (!Number.isInteger(age) || age < 0) {
throw new Error(`invalid age: ${text}`);
}
return age;
}
try {
parseAge("abc");
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
console.log(message); // invalid age: abc
}一个完整的小程序:带类型的库存
interface Item {
name: string;
price: number;
qty: number;
}
type Summary = { total: number; cheapest?: Item; outOfStock: string[] };
function summarize(items: Item[]): Summary {
let total = 0;
let cheapest: Item | undefined;
const outOfStock: string[] = [];
for (const item of items) {
total += item.price * item.qty;
if (item.qty === 0) outOfStock.push(item.name);
if (!cheapest || item.price < cheapest.price) cheapest = item;
}
return { total, cheapest, outOfStock };
}
const result = summarize([
{ name: "pen", price: 1.5, qty: 10 },
{ name: "book", price: 12, qty: 2 },
{ name: "bag", price: 30, qty: 0 },
]);
console.log(result.total, result.cheapest?.name, result.outOfStock);
// 39 pen [ 'bag' ]试着把 qty: 10 改成 qty: "10":编译器会在代码运行之前就报错。
新手常见错误
- 用
any来消除报错。更好的做法是使用unknown并对其收窄。 - 过度使用
as类型断言,这等于让编译器无条件相信你,而不是去检查。 - 在
tsconfig.json中不开启strict。新项目请打开它,它会启用null检查。 - 写出编译器本来就能推断的类型,增加了噪音却没有带来安全性。
接下来学什么
阅读官方的 TypeScript Handbook,并在 typescriptlang.org 的 TypeScript Playground 中动手实验,它会并排显示编译后的 JavaScript 和错误信息。然后学习 tsconfig.json 的选项、库的类型声明文件(@types/... 包),以及在你选用的框架中使用 TypeScript。
常见问题
TypeScript 和 JavaScript 是不同的语言吗?
它是 JavaScript 的超集:增加了类型语法和检查器,然后编译成普通 JavaScript。运行时行为与它生成的 JavaScript 完全一致。
应该先学 JavaScript 再学 TypeScript 吗?
先学好 JavaScript 基础(变量、函数、对象、Promise),因为 TypeScript 不会改变这些内容。然后再加入 TypeScript;熟悉运行时行为之后,类型会更容易学。
interface 和 type 有什么区别?
两者都能描述对象的形状。接口可以被扩展,也可以通过再次声明来合并;类型别名还可以为联合类型、元组和其他非对象类型命名。对于普通的对象形状,两者都可以。
TypeScript 会让我的代码运行得更快吗?
不会。类型在编译时被移除,所以输出的代码与手写 JavaScript 速度相同。它的好处是更少的 bug 和更好的工具支持。