====== 第一章:TypeScript 基础 ======
===== 本章概述 =====
欢迎来到 TypeScript 的世界!本章将带你从零开始了解 TypeScript,包括安装配置、编译原理以及最基本的类型注解概念。通过本章学习,你将能够搭建 TypeScript 开发环境并编写第一个 TypeScript 程序。
===== 1.1 什么是 TypeScript =====
TypeScript 是一种由 Microsoft 开发的开源编程语言,于 2012 年 10 月首次发布。它是 JavaScript 的超集(Superset),意味着任何有效的 JavaScript 代码都是有效的 TypeScript 代码。
==== 1.1.1 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 版本:
node --version
npm --version
==== 1.2.2 全局安装 TypeScript ====
使用 npm 全局安装 TypeScript 编译器:
# 使用 npm
npm install -g typescript
# 使用 yarn
yarn global add typescript
验证安装:
tsc --version
# 输出类似:Version 5.3.3
==== 1.2.3 项目本地安装(推荐) ====
对于实际项目,建议在项目本地安装 TypeScript:
# 创建项目目录
mkdir my-ts-project
cd my-ts-project
# 初始化 npm 项目
npm init -y
# 本地安装 TypeScript
npm install --save-dev typescript
# 或作为开发依赖
npm install -D typescript
本地安装后,使用 npx 运行 tsc:
npx tsc --version
===== 1.3 编写第一个 TypeScript 程序 =====
==== 1.3.1 创建 TypeScript 文件 ====
创建一个名为 "hello.ts" 的文件:
// 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 命令编译文件:
tsc hello.ts
这将生成一个同名的 JavaScript 文件 "hello.js":
// 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 ====
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
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
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 变量类型注解 ====
使用冒号(:)为变量添加类型注解:
// 基本类型
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 函数类型注解 ====
// 函数声明
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 对象类型注解 ====
// 对象字面量类型
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 初始化配置文件 ====
tsc --init
这将创建一个包含所有编译器选项的 tsconfig.json 文件。
==== 1.6.2 基本配置示例 ====
{
"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 会自动推断类型
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 函数返回值推断 ====
// 返回值类型可以被推断
function add(a: number, b: number) {
return a + b; // 推断返回值为 number
}
// 复杂推断
function getArray() {
return [1, 2, 3]; // 推断为 number[]
}
==== 1.7.3 最佳实践:何时添加类型注解 ====
**应该添加类型注解的情况:**
- 函数参数
- 函数返回值(复杂逻辑时)
- 类的公共属性
- 需要明确类型的变量
**可以依赖推断的情况:**
- 简单变量的初始化
- 明显能从上下文推断的类型
- 局部变量
// 推荐:为函数参数和返回值添加类型
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 语法 ====
// 尖括号语法
let someValue: any = "this is a string";
let strLength: number = (someValue).length;
// as 语法(推荐,特别是在 JSX 中)
let strLength2: number = (someValue as string).length;
==== 1.8.2 使用场景 ====
// 处理 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 允许将字面量作为类型使用,提供更精确的类型约束。
// 字符串字面量类型
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 ====
// 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 ====
// 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:类型注解基础 ====
为以下变量和函数添加适当的类型注解:
// 待补充类型的代码
let userName = "张三";
let userAge = 25;
let hobbies = ["阅读", "编程", "游戏"];
function greetUser(name, age) {
return `你好,${name},今年${age}岁`;
}
function calculateArea(width, height) {
return width * height;
}
参考答案
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
参考答案
{
"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:类型推断与断言 ====
分析以下代码,指出哪些地方使用了类型推断,哪些地方需要类型断言:
const apiResponse = fetchData();
const userName = apiResponse.name;
const userAge = apiResponse.age;
const element = document.getElementById("input");
const value = element.value;
参考答案与解释
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 之一
参考答案
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 官方文档 - 编译配置]]