欢迎来到 TypeScript 的世界!本章将带你从零开始了解 TypeScript,包括安装配置、编译原理以及最基本的类型注解概念。通过本章学习,你将能够搭建 TypeScript 开发环境并编写第一个 TypeScript 程序。
TypeScript 是一种由 Microsoft 开发的开源编程语言,于 2012 年 10 月首次发布。它是 JavaScript 的超集(Superset),意味着任何有效的 JavaScript 代码都是有效的 TypeScript 代码。
静态类型系统 TypeScript 最大的特点是添加了可选的静态类型系统。这使得开发者可以在编码阶段就发现类型错误,而不是在运行时。
```typescript JavaScript - 运行时才发现错误 function add(a, b) { return a + b; } add(“1”, 2); “12” - 逻辑错误但未报错
TypeScript - 编译时就发现错误 function addTS(a: number, b: number): number { return a + b; } addTS(“1”, 2); Error: Argument of type 'string' is not assignable to parameter of type 'number' ```
ES6+ 特性支持 TypeScript 支持最新的 ECMAScript 特性,包括:
编译时转换 TypeScript 代码需要编译(转译)为 JavaScript 才能在浏览器或 Node.js 中运行。编译器会将高级语法转换为兼容目标环境的 JavaScript。
| 特性 | JavaScript | TypeScript |
| —— | ———— | ———— |
| 类型系统 | 动态类型 | 静态类型(可选) |
| 编译 | 解释执行 | 需要编译 |
| 错误检测 | 运行时 | 编译时 |
| IDE 支持 | 基础 | 强大 |
| 学习曲线 | 平缓 | 稍陡 |
| 代码量 | 较少 | 稍多(类型注解) |
| 维护性 | 一般 | 优秀 |
在开始之前,请确保你的系统已安装:
检查 Node.js 版本: ```bash node –version npm –version ```
使用 npm 全局安装 TypeScript 编译器:
```bash # 使用 npm npm install -g typescript
# 使用 yarn yarn global add typescript ```
验证安装: ```bash tsc –version # 输出类似:Version 5.3.3 ```
对于实际项目,建议在项目本地安装 TypeScript:
```bash # 创建项目目录 mkdir my-ts-project cd my-ts-project
# 初始化 npm 项目 npm init -y
# 本地安装 TypeScript npm install –save-dev typescript
# 或作为开发依赖 npm install -D typescript ```
本地安装后,使用 npx 运行 tsc: ```bash npx tsc –version ```
创建一个名为 “hello.ts” 的文件:
```typescript hello.ts function greet(person: string, date: Date): string { return `Hello ${person}, today is ${date.toDateString()}!`; } const user = “TypeScript”; const today = new Date(); console.log(greet(user, today)); ``` ==== 1.3.2 编译 TypeScript ==== 使用 tsc 命令编译文件: ```bash tsc hello.ts ``` 这将生成一个同名的 JavaScript 文件 “hello.js”: ```javascript hello.js (编译输出) function greet(person, date) {
return "Hello ".concat(person, ", today is ").concat(date.toDateString(), "!");
} var user = “TypeScript”; var today = new Date(); console.log(greet(user, today)); ```
```bash node hello.js # 输出:Hello TypeScript, today is Mon Jan 15 2025! ```
TypeScript 编译器(tsc)的工作流程:
``` TypeScript 源码 → 词法分析 → 语法分析 → 语义分析 → 类型检查 → 代码生成 → JavaScript 代码 ```
1. 词法分析(Lexical Analysis) 将源代码转换为 Token 序列。
2. 语法分析(Parsing) 将 Token 序列转换为抽象语法树(AST)。
3. 语义分析(Semantic Analysis) 检查语法结构的语义正确性。
4. 类型检查(Type Checking) 这是 TypeScript 的核心阶段,验证类型系统的约束。
5. 代码生成(Code Generation) 将 AST 转换为目标 JavaScript 代码。
TypeScript 的类型只在编译时存在,编译后的 JavaScript 代码中完全不包含类型信息,这个过程称为“类型擦除”(Type Erasure)。
```typescript TypeScript interface Point { x: number; y: number; } function distance(p1: Point, p2: Point): number { const dx = p1.x - p2.x; const dy = p1.y - p2.y; return Math.sqrt(dx * dx + dy * dy); } ``` 编译后: ```javascript JavaScript function distance(p1, p2) {
var dx = p1.x - p2.x; var dy = p1.y - p2.y; return Math.sqrt(dx * dx + dy * dy);
} ```
注意:接口定义和类型注解在编译后完全消失!
使用冒号(:)为变量添加类型注解:
```typescript 基本类型 let name: string = “Alice”; let age: number = 25; let isStudent: boolean = true; 数组类型 let numbers: number[] = [1, 2, 3, 4, 5]; let names: Array<string> = [“Alice”, “Bob”, “Charlie”];
any 类型 - 绕过类型检查 let anything: any = 4; anything = “string”; anything = true; unknown 类型 - 类型安全的 any let notSure: unknown = 4; notSure.toFixed(); Error: Object is of type 'unknown'
void - 无返回值 function logMessage(message: string): void { console.log(message); } null 和 undefined let u: undefined = undefined; let n: null = null; ```
```typescript 函数声明 function add(x: number, y: number): number { return x + y; } 函数表达式 const multiply = function(x: number, y: number): number {
return x * y;
};
箭头函数 const divide = (x: number, y: number): number ⇒ { return x / y; }; 可选参数 function greet(name: string, greeting?: string): string {
if (greeting) {
return `${greeting}, ${name}!`;
}
return `Hello, ${name}!`;
}
默认参数 function greetWithDefault(name: string, greeting: string = “Hello”): string { return `${greeting}, ${name}!`; } 剩余参数 function sum(…numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
} ```
```typescript 对象字面量类型 let person: { name: string; age: number; isStudent?: boolean; 可选属性 } = {
name: "Alice", age: 25
};
使用接口(推荐) interface Person { name: string; age: number; email?: string; } const alice: Person = { name: “Alice”, age: 25, email: “alice@example.com” }; ``` ===== 1.6 tsconfig.json 配置 ===== ==== 1.6.1 初始化配置文件 ==== ```bash tsc –init ``` 这将创建一个包含所有编译器选项的 tsconfig.json 文件。 ==== 1.6.2 基本配置示例 ==== ```json { “compilerOptions”: { 目标 JavaScript 版本
"target": "ES2020", // 模块系统 "module": "ESNext", // 模块解析策略 "moduleResolution": "node", // 输出目录 "outDir": "./dist", // 源代码目录 "rootDir": "./src", // 启用严格模式 "strict": true, // 允许编译 JavaScript 文件 "allowJs": true, // 生成 source map "sourceMap": true, // 启用装饰器 "experimentalDecorators": true, // 启用 ES 模块互操作 "esModuleInterop": true, // 跳过库类型检查 "skipLibCheck": true, // 强制文件名大小写一致 "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"]
} ```
target 指定编译后的 JavaScript 版本:“ES3”, “ES5”, “ES2015”, “ES2016”, “ES2017”, “ES2018”, “ES2019”, “ES2020”, “ESNext”
module 指定模块系统:“CommonJS”, “AMD”, “System”, “UMD”, “ES2015”, “ES2020”, “ESNext”
strict 启用所有严格类型检查选项的快捷方式,等同于开启:
noImplicitAny 禁止隐式的 any 类型,要求必须为没有类型注解的变量或参数指定类型。
TypeScript 具有强大的类型推断能力,在很多情况下不需要显式添加类型注解。
```typescript TypeScript 会自动推断类型 let message = “Hello”; 推断为 string let count = 42; 推断为 number let isValid = true; 推断为 boolean
错误示例 message = 42; Error: Type 'number' is not assignable to type 'string' ```
```typescript 返回值类型可以被推断 function add(a: number, b: number) { return a + b; 推断返回值为 number }
复杂推断 function getArray() { return [1, 2, 3]; 推断为 number[] } ```
应该添加类型注解的情况:
可以依赖推断的情况:
```typescript 推荐:为函数参数和返回值添加类型 function calculateTotal(price: number, quantity: number): number { return price * quantity; } 推荐:复杂对象明确类型 interface User {
id: number; name: string;
}
const users: User[] = fetchUsers();
可选:简单变量可以依赖推断 const total = calculateTotal(100, 5); const welcomeMessage = “Welcome!”; ``` ===== 1.8 类型断言 ===== 类型断言(Type Assertion)允许你告诉编译器某个值的具体类型,类似于其他语言中的类型转换。 ==== 1.8.1 语法 ==== ```typescript 尖括号语法 let someValue: any = “this is a string”; let strLength: number = (<string>someValue).length;
as 语法(推荐,特别是在 JSX 中) let strLength2: number = (someValue as string).length; ``` ==== 1.8.2 使用场景 ==== ```typescript 处理 DOM 元素 const input = document.getElementById(“user-input”) as HTMLInputElement; console.log(input.value);
处理 API 响应 interface User { name: string; age: number; } const response: any = fetchUser(); const user = response as User; 双重断言(谨慎使用) const something = “hello” as unknown as number; 强制转换 ``` ===== 1.9 字面量类型 ===== TypeScript 允许将字面量作为类型使用,提供更精确的类型约束。 ```typescript 字符串字面量类型 let direction: “north” | “south” | “east” | “west”; direction = “north”; ✓ direction = “up”; ✗ Error 数字字面量类型 type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6; let roll: DiceRoll = 4; ✓ let invalid: DiceRoll = 7; ✗ Error 布尔字面量类型 type Status = true | false;
结合使用 interface Config { mode: “development” | “production” | “test”; port: 3000 | 8080 | 9000; debug: boolean; } ``` ===== 1.10 严格模式详解 ===== ==== 1.10.1 strictNullChecks ==== ```typescript strictNullChecks: false(默认) let name: string = null; OK strictNullChecks: true let name2: string = null; Error: Type 'null' is not assignable to type 'string' 正确使用 let name3: string | null = null; OK ``` ==== 1.10.2 noImplicitAny ==== ```typescript noImplicitAny: false function log(message) { 参数隐式为 any console.log(message); } noImplicitAny: true function log2(message: any) { 必须显式声明 any console.log(message); } ``` ===== 1.11 本章小结 ===== 本章我们学习了: 1. TypeScript 基础概念 - 理解 TypeScript 是什么以及它与 JavaScript 的关系 2. 环境搭建 - 安装 TypeScript 编译器并创建第一个程序 3. 编译原理 - 了解 TypeScript 如何编译为 JavaScript 4. 类型注解 - 掌握基本类型的使用方法 5. tsconfig.json - 配置编译器选项 6. 类型推断 - 理解何时需要显式添加类型 7. 类型断言 - 在必要时覆盖类型推断 ===== 1.12 练习题 ===== ==== 练习 1:类型注解基础 ==== 为以下变量和函数添加适当的类型注解: ```typescript 待补充类型的代码 let userName = “张三”; let userAge = 25; let hobbies = [“阅读”, “编程”, “游戏”];
function greetUser(name, age) {
return `你好,${name},今年${age}岁`;
}
function calculateArea(width, height) {
return width * height;
} ```
<details> <summary>参考答案</summary>
```typescript let userName: string = “张三”; let userAge: number = 25; let hobbies: string[] = [“阅读”, “编程”, “游戏”];
function greetUser(name: string, age: number): string {
return `你好,${name},今年${age}岁`;
}
function calculateArea(width: number, height: number): number {
return width * height;
} ```
</details>
创建一个适合前端项目的 tsconfig.json 配置,要求:
<details> <summary>参考答案</summary>
```json {
"compilerOptions": {
"target": "ES2018",
"module": "ESNext",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
} ```
</details>
分析以下代码,指出哪些地方使用了类型推断,哪些地方需要类型断言:
```typescript const apiResponse = fetchData(); const userName = apiResponse.name; const userAge = apiResponse.age;
const element = document.getElementById(“input”); const value = element.value; ```
<details> <summary>参考答案与解释</summary>
```typescript const apiResponse: any = fetchData(); 需要类型,默认推断为 any const userName: string = apiResponse.name; 从上下文推断,或需要断言 const userAge: number = apiResponse.age;
需要类型断言,因为 getElementById 返回 HTMLElement | null const element = document.getElementById(“input”) as HTMLInputElement; const value = element.value; 或者先进行类型检查 const element2 = document.getElementById(“input”); if (element2 instanceof HTMLInputElement) {
const value2 = element2.value; // 这里 TypeScript 知道类型
} ```
</details>
使用字面量类型定义一个配置对象,要求:
<details> <summary>参考答案</summary>
```typescript type Environment = “dev” | “test” | “prod”; type LogLevel = “debug” | “info” | “warn” | “error”; type Port = 3000 | 8080 | 9000;
interface AppConfig {
environment: Environment; logLevel: LogLevel; port: Port;
}
const config: AppConfig = {
environment: "dev", logLevel: "debug", port: 3000
}; ```
</details>