TypeScript 语言完整入门指南
TypeScript(简称 TS)是 JavaScript 的一个超集,它在 JavaScript 的基础上添加了静态类型系统和面向对象特性。TypeScript 代码不能直接被浏览器或 Node.js 执行,需要编译(Transpile)成标准的 JavaScript 代码后方可运行。它的核心价值在于“在编译期(而不是运行期)发现 Bug”。
1. TypeScript 简介与环境搭建
1.1 核心价值
- 编译期类型检查:在代码写完保存的那一刻,如果有参数传错、变量名写错,IDE 和编译器会立刻报错,避免带到线上。
- 极佳的智能提示 (Autocompletion):有了类型定义,你可以轻松看到对象的属性和方法提示,不需要去翻文档。
- 安全重构:当你要修改一个底层的字段名字,编译器会自动找出全项目所有用到该字段的地方并报错,提示你进行修改,避免漏改。
1.2 环境搭建
- TS 的编译需要 Node.js 运行环境支持。
- 全局安装 TS 编译器 (
tsc):npm install -g typescript - 验证安装:
tsc -v - 本地极速运行工具 (ts-node):
正常开发需要先
tsc file.ts生成 JS 才能跑。本地测试可以使用ts-node直接跳过编译步骤运行:npm install -g ts-node ts-node file.ts
2. 基础类型注解 (Type Annotation)
TS 的核心是在变量后使用 : Type 语法声明类型。
// 1. 基础基本类型
const username: string = "Bob";
const age: number = 27;
const isActive: boolean = true;
// 2. 数组类型 (两种写法)
const scores: number[] = [95, 88, 76];
const fruits: Array<string> = ["apple", "orange"];
// 3. 元组 (Tuple - 长度固定、类型确定的特殊数组)
const userRole: [number, string] = [1, "admin"];
// 4. 枚举 (Enum)
enum Status {
Pending, // 默认从 0 开始
Success, // 1
Failed // 2
}
const currentStatus: Status = Status.Success;3. 接口 (Interface) 与 类型别名 (Type Alias)
当需要声明复杂的对象结构时,可以使用 interface 或 type。
3.1 接口 (Interface)
用于定义对象(Object)的结构规范:
interface User {
id: number;
name: string;
email: string;
phone?: string; // 问号 ? 表示该属性是可选的 (Optional)
readonly created: Date; // 只读属性,赋值后不可修改
}
const u: User = {
id: 1,
name: "Alice",
email: "alice@example.com",
created: new Date()
};
// u.created = new Date(); // 报错:只读属性不可修改3.2 类型别名 (Type)
type 比 interface 更灵活,可以定义任意类型(包括联合类型、交叉类型):
type ID = string | number; // 联合类型 (ID 可以是 string 或 number)
let userId: ID = 123;
userId = "usr_999"; // 合法
type UserWithRole = User & { role: string }; // 交叉类型 (合二为一)4. 函数类型定义
TS 允许你对函数的输入参数和返回值进行强类型约束:
// 限制参数 x, y 必须是数字,且返回值必须是数字
function add(x: number, y: number): number {
return x + y;
}
// 箭头函数类型定义 (不返回值使用 void)
const logMessage = (msg: string): void => {
console.log(msg);
};5. 泛型 (Generics)
泛型是指在定义函数、接口或类时不预先指定具体类型,而是在使用时才动态确定类型的设计模式,是编写可复用代码的核心。
// <T> 代表占位符类型,接收什么类型,返回值就是什么类型
function identity<T>(arg: T): T {
return arg;
}
// 明确传入数字类型,此时 T 被替换为 number
let output1 = identity<number>(100);
// 传入字符串,T 被替换为 string
let output2 = identity<string>("hello");6. 进阶工具类型 (Utility Types)
TS 官方内置了一套转换现有类型的工具,非常常用:
interface Todo {
id: number;
title: string;
description: string;
}
// 1. Partial<T> : 将所有属性变成可选
type OptionalTodo = Partial<Todo>; // 属性全部带上问号 ?
// 2. Pick<T, Keys> : 仅拣选出一部分属性组成新类型
type SimpleTodo = Pick<Todo, "id" | "title">; // 只有 id 和 title
// 3. Omit<T, Keys> : 排除掉某些属性后组成新类型
type CustomTodo = Omit<Todo, "description">; // 排除掉 description7. 配置文件 tsconfig.json
TS 项目的根目录下通常会有一个 tsconfig.json 文件,用于控制编译器的编译规则。
{
"compilerOptions": {
"target": "es2022", // 编译出的 JS 语法标准
"module": "commonjs", // 模块规范
"strict": true, // 开启所有严格类型检查 (最关键)
"esModuleInterop": true, // 兼容 ES 模块导入
"skipLibCheck": true, // 跳过对第三方声明文件的类型检查
"forceConsistentCasingInFileNames": true
}
}在现代开发中(特别是使用 Next.js 框架时),脚手架会自动帮你生成并管理好该配置文件。
💡 经典避坑与抓虫小测验
以下小测验选取了 TypeScript 类型系统中最具欺骗性、最容易混淆的规则陷阱。请尝试阅读并分析以下代码,预测其编译结果或找出 Bug,点击“答案与解析”查看具体原因。
测验 1:对象字面量的“双标”类型检查
【题目】:以下这段 TS 代码中,第 15 行和第 18 行的赋值哪一行能通过编译?为什么?
interface User {
name: string;
age: number;
}
const info = {
name: "Bob",
age: 30,
email: "bob@example.com" // 多了一个 email 属性
};
// 赋值 1
const u1: User = info;
// 赋值 2
const u2: User = {
name: "Bob",
age: 30,
email: "bob@example.com"
};👉 答案与解析
【编译结果】:
* 赋值 1:可以通过编译,运行完全正常。
* 赋值 2:编译报错:Type '{ name: string; age: number; email: string; }' is not assignable to type 'User'. Object literal may only specify known properties, and 'email' does not exist in type 'User'.
【原因分析】
额外属性检查(Excess Property Checking)
对于赋值 2
对象字面量
对于赋值 1
infoinfou1结构化类型系统(Structural Typing)
infoUsernameage测验 2:any 与 unknown 的防线
【题目】:以下代码中,哪一行会编译报错?说明 any 与 unknown 的核心区别。
let valueAny: any = "hello";
let valueUnknown: unknown = "hello";
// 行 5
valueAny.trim();
// 行 8
valueUnknown.trim();👉 答案与解析
【编译结果】:
* 行 5:可以通过编译,运行完全正常。
* 行 8:编译报错:'valueUnknown' is of type 'unknown'.
【原因分析】
any
anyunknown
类型安全的 any
unknown类型守卫(Type Guard)
修改方法
typescript if (typeof valueUnknown === "string") { valueUnknown.trim(); // 类型收窄为 string,编译通过 }测验 3:同名接口(Interface)的自动合并陷阱
【题目】:预测以下代码的编译结果:
interface Window {
title: string;
}
interface Window {
width: number;
}
const w: Window = {
title: "Vibe Platform",
width: 1024
};👉 答案与解析
【编译结果】: 完全可以通过编译。
【原因分析】
同名的 interface
合并(Declaration Merging)
Windowtitlewidth注意点
typetypeDuplicate identifier测验 4:数字枚举(Enum)的双向通道漏洞
【题目】:以下这段代码能否正常编译运行?如果能,最终会输出什么?
enum Direction {
Up = 1,
Down = 2
}
console.log(Direction[1]);
console.log(Direction["Up"]);👉 答案与解析
【输出结果】:
"Up"
1【原因分析】: 在 TypeScript 中,数字型枚举支持反向映射(Reverse Mapping)。编译成 JS 后,它的数据结构其实被写成了双向的:
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 1] = "Up";
Direction[Direction["Down"] = 2] = "Down";
})(Direction || (Direction = {}));"Up"11"Up"避坑警告
Up = "UP"不支持
undefinedtype Direction = "UP" | "DOWN"