====== 第一章:TypeScript 基础 ====== ===== 本章概述 ===== 欢迎来到 TypeScript 的世界!本章将带你从零开始了解 TypeScript,包括安装配置、编译原理以及最基本的类型注解概念。通过本章学习,你将能够搭建 TypeScript 开发环境并编写第一个 TypeScript 程序。 ===== 1.1 什么是 TypeScript ===== TypeScript 是一种由 Microsoft 开发的开源编程语言,于 2012 年 10 月首次发布。它是 JavaScript 的超集(Superset),意味着任何有效的 JavaScript 代码都是有效的 TypeScript 代码。 ==== 1.1.1 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 特性,包括: - 类(Class) - 模块(Module) - 异步函数(Async/Await) - 解构赋值 - 箭头函数 - 等等 **编译时转换** TypeScript 代码需要编译(转译)为 JavaScript 才能在浏览器或 Node.js 中运行。编译器会将高级语法转换为兼容目标环境的 JavaScript。 ==== 1.1.2 TypeScript vs JavaScript ==== | 特性 | JavaScript | TypeScript | |------|------------|------------| | 类型系统 | 动态类型 | 静态类型(可选) | | 编译 | 解释执行 | 需要编译 | | 错误检测 | 运行时 | 编译时 | | IDE 支持 | 基础 | 强大 | | 学习曲线 | 平缓 | 稍陡 | | 代码量 | 较少 | 稍多(类型注解) | | 维护性 | 一般 | 优秀 | ===== 1.2 安装 TypeScript ===== ==== 1.2.1 环境要求 ==== 在开始之前,请确保你的系统已安装: - Node.js(推荐 v18 或更高版本) - npm 或 yarn 包管理器 检查 Node.js 版本: ```bash node --version npm --version ``` ==== 1.2.2 全局安装 TypeScript ==== 使用 npm 全局安装 TypeScript 编译器: ```bash # 使用 npm npm install -g typescript # 使用 yarn yarn global add typescript ``` 验证安装: ```bash tsc --version # 输出类似:Version 5.3.3 ``` ==== 1.2.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 ``` ===== 1.3 编写第一个 TypeScript 程序 ===== ==== 1.3.1 创建 TypeScript 文件 ==== 创建一个名为 "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)); ``` ==== 1.3.3 运行 JavaScript ==== ```bash node hello.js # 输出:Hello TypeScript, today is Mon Jan 15 2025! ``` ===== 1.4 TypeScript 编译原理 ===== ==== 1.4.1 编译过程概述 ==== TypeScript 编译器(tsc)的工作流程: ``` TypeScript 源码 → 词法分析 → 语法分析 → 语义分析 → 类型检查 → 代码生成 → JavaScript 代码 ``` **1. 词法分析(Lexical Analysis)** 将源代码转换为 Token 序列。 **2. 语法分析(Parsing)** 将 Token 序列转换为抽象语法树(AST)。 **3. 语义分析(Semantic Analysis)** 检查语法结构的语义正确性。 **4. 类型检查(Type Checking)** 这是 TypeScript 的核心阶段,验证类型系统的约束。 **5. 代码生成(Code Generation)** 将 AST 转换为目标 JavaScript 代码。 ==== 1.4.2 类型擦除 ==== 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); } ``` 注意:接口定义和类型注解在编译后完全消失! ===== 1.5 基础类型注解 ===== ==== 1.5.1 变量类型注解 ==== 使用冒号(:)为变量添加类型注解: ```typescript // 基本类型 let name: string = "Alice"; let age: number = 25; let isStudent: boolean = true; // 数组类型 let numbers: number[] = [1, 2, 3, 4, 5]; let names: Array = ["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; ``` ==== 1.5.2 函数类型注解 ==== ```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); } ``` ==== 1.5.3 对象类型注解 ==== ```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"] } ``` ==== 1.6.3 常用编译选项详解 ==== **target** 指定编译后的 JavaScript 版本:"ES3", "ES5", "ES2015", "ES2016", "ES2017", "ES2018", "ES2019", "ES2020", "ESNext" **module** 指定模块系统:"CommonJS", "AMD", "System", "UMD", "ES2015", "ES2020", "ESNext" **strict** 启用所有严格类型检查选项的快捷方式,等同于开启: - strictNullChecks - strictFunctionTypes - strictBindCallApply - strictPropertyInitialization - noImplicitAny - noImplicitThis - alwaysStrict **noImplicitAny** 禁止隐式的 any 类型,要求必须为没有类型注解的变量或参数指定类型。 ===== 1.7 类型推断 ===== TypeScript 具有强大的类型推断能力,在很多情况下不需要显式添加类型注解。 ==== 1.7.1 变量类型推断 ==== ```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' ``` ==== 1.7.2 函数返回值推断 ==== ```typescript // 返回值类型可以被推断 function add(a: number, b: number) { return a + b; // 推断返回值为 number } // 复杂推断 function getArray() { return [1, 2, 3]; // 推断为 number[] } ``` ==== 1.7.3 最佳实践:何时添加类型注解 ==== **应该添加类型注解的情况:** - 函数参数 - 函数返回值(复杂逻辑时) - 类的公共属性 - 需要明确类型的变量 **可以依赖推断的情况:** - 简单变量的初始化 - 明显能从上下文推断的类型 - 局部变量 ```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 = (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; } ```
参考答案 ```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; } ```
==== 练习 2:配置 tsconfig.json ==== 创建一个适合前端项目的 tsconfig.json 配置,要求: - 目标 ES2018 - 使用 ES 模块 - 输出到 dist 目录 - 源代码在 src 目录 - 启用严格模式 - 生成 source map
参考答案 ```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"] } ```
==== 练习 3:类型推断与断言 ==== 分析以下代码,指出哪些地方使用了类型推断,哪些地方需要类型断言: ```typescript const apiResponse = fetchData(); const userName = apiResponse.name; const userAge = apiResponse.age; const element = document.getElementById("input"); const value = element.value; ```
参考答案与解释 ```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 知道类型 } ```
==== 练习 4:字面量类型 ==== 使用字面量类型定义一个配置对象,要求: - 环境只能是 "dev", "test", "prod" 之一 - 日志级别只能是 "debug", "info", "warn", "error" 之一 - 端口号只能是 3000, 8080, 9000 之一
参考答案 ```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 }; ```
===== 扩展阅读 ===== - [[TypeScript:第二章_变量声明|下一章:变量声明]] - [[https://www.typescriptlang.org/docs/handbook/basic-types.html|TypeScript 官方文档 - 基础类型]] - [[https://www.typescriptlang.org/tsconfig|TypeScript 官方文档 - 编译配置]]