TypeScript-01-类型系统基础
类型系统是 TypeScript 的核心,掌握它是高效使用 TypeScript 的基础。
1. 基本类型
1.1 原始类型
typescript
// 字符串
let name: string = "TypeScript";
// 数字(整数和浮点数统一为 number)
let age: number = 25;
let price: number = 99.99;
// 布尔值
let isActive: boolean = true;
// ES6 新增的原始类型
let uniqueId: symbol = Symbol("id");
let bigNumber: bigint = 100n;1.2 特殊类型
typescript
// null 和 undefined
let empty: null = null;
let notDefined: undefined = undefined;
// void - 表示没有返回值
function logMessage(msg: string): void {
console.log(msg);
}
// never - 表示永远不会有返回值
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
// unknown - 类型安全的 any
let userInput: unknown;
userInput = 5;
userInput = "hello";
// 使用前必须进行类型检查
if (typeof userInput === "string") {
console.log(userInput.toUpperCase());
}
// any - 放弃类型检查(尽量避免使用)
let flexible: any = "anything";
flexible = 123;
flexible.nonExistentMethod(); // 不会报错,但运行时会出错1.3 数组与元组
typescript
// 数组的两种写法
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ["a", "b", "c"];
// 只读数组
let readonlyArr: readonly number[] = [1, 2, 3];
// readonlyArr.push(4); // 错误:不能修改只读数组
// 元组 - 固定长度和类型的数组
let tuple: [string, number] = ["hello", 42];
let [greeting, count] = tuple; // 解构赋值
// 可选元素的元组
let optionalTuple: [string, number?] = ["hello"];
// 剩余元素的元组
let restTuple: [string, ...number[]] = ["hello", 1, 2, 3];
// 命名元组(TypeScript 4.0+)
type NamedTuple = [name: string, age: number];2. 对象类型
2.1 对象字面量类型
typescript
// 直接定义对象类型
let user: { name: string; age: number } = {
name: "Alice",
age: 25,
};
// 可选属性
let config: { host: string; port?: number } = {
host: "localhost",
};
// 只读属性
let point: { readonly x: number; readonly y: number } = {
x: 10,
y: 20,
};
// point.x = 5; // 错误:不能修改只读属性
// 索引签名
let dictionary: { [key: string]: string } = {
hello: "你好",
world: "世界",
};2.2 接口 (Interface)
typescript
// 基本接口定义
interface User {
id: number;
name: string;
email: string;
age?: number; // 可选属性
readonly createdAt: Date; // 只读属性
}
// 使用接口
const alice: User = {
id: 1,
name: "Alice",
email: "alice@example.com",
createdAt: new Date(),
};
// 接口继承
interface Employee extends User {
department: string;
salary: number;
}
// 多重继承
interface Manager extends Employee {
subordinates: Employee[];
}
// 接口合并(同名接口会自动合并)
interface Config {
host: string;
}
interface Config {
port: number;
}
// 等同于 interface Config { host: string; port: number; }2.3 类型别名 (Type Alias)
typescript
// 基本类型别名
type ID = string | number;
type Point = { x: number; y: number };
// 与接口的区别
// 1. 类型别名可以定义联合类型、交叉类型、元组等
type Status = "pending" | "approved" | "rejected";
type Coordinate = [number, number];
// 2. 类型别名不能被重复声明(接口可以合并)
// type Status = "active"; // 错误:重复声明
// 3. 类型别名使用交叉类型实现扩展
type ExtendedPoint = Point & { z: number };2.4 接口 vs 类型别名
typescript
// 推荐使用场景
// 使用 interface:
// - 定义对象结构
// - 需要被类实现(implements)
// - 需要声明合并
interface Animal {
name: string;
speak(): void;
}
class Dog implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak() {
console.log("Woof!");
}
}
// 使用 type:
// - 定义联合类型、交叉类型
// - 定义元组
// - 定义函数类型
// - 定义复杂的类型运算
type Result<T> = { success: true; data: T } | { success: false; error: string };
type Handler = (event: Event) => void;3. 联合类型与交叉类型
3.1 联合类型 (Union Types)
typescript
// 基本联合类型
type StringOrNumber = string | number;
function printId(id: StringOrNumber) {
// 类型收窄
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id.toFixed(2));
}
}
// 字面量联合类型
type Direction = "up" | "down" | "left" | "right";
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
function move(direction: Direction) {
console.log(`Moving ${direction}`);
}
// 可辨识联合(Discriminated Unions)
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
type Shape = Circle | Square | Rectangle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
case "rectangle":
return shape.width * shape.height;
}
}3.2 交叉类型 (Intersection Types)
typescript
// 合并多个类型
interface HasName {
name: string;
}
interface HasAge {
age: number;
}
interface HasEmail {
email: string;
}
type Person = HasName & HasAge & HasEmail;
const person: Person = {
name: "Alice",
age: 25,
email: "alice@example.com",
};
// 常用于 mixin 模式
function withTimestamp<T extends object>(obj: T): T & { timestamp: Date } {
return { ...obj, timestamp: new Date() };
}
const timestamped = withTimestamp({ name: "Alice" });
// timestamped 的类型是 { name: string } & { timestamp: Date }4. 类型推断
4.1 基本推断
typescript
// 变量初始化时推断
let message = "Hello"; // 推断为 string
let count = 42; // 推断为 number
let isReady = true; // 推断为 boolean
// 数组推断
let numbers = [1, 2, 3]; // 推断为 number[]
let mixed = [1, "two", true]; // 推断为 (string | number | boolean)[]
// 对象推断
let user = {
name: "Alice",
age: 25,
}; // 推断为 { name: string; age: number }4.2 最佳公共类型
typescript
// TypeScript 会找出最佳公共类型
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
let pets = [new Dog(), new Cat()]; // 推断为 (Dog | Cat)[]
// 如果需要推断为基类,需要显式指定
let animals: Animal[] = [new Dog(), new Cat()];4.3 上下文类型
typescript
// 根据上下文推断参数类型
const names = ["Alice", "Bob", "Charlie"];
// forEach 的回调参数自动推断为 string
names.forEach((name) => {
console.log(name.toUpperCase()); // name 被推断为 string
});
// 事件处理器的上下文推断
document.addEventListener("click", (event) => {
// event 被推断为 MouseEvent
console.log(event.clientX, event.clientY);
});4.4 const 断言
typescript
// 普通声明
let colors = ["red", "green", "blue"]; // string[]
// as const 断言 - 将类型收窄为字面量类型
const colorsConst = ["red", "green", "blue"] as const;
// 类型为 readonly ["red", "green", "blue"]
// 对象的 const 断言
const config = {
host: "localhost",
port: 3000,
} as const;
// 类型为 { readonly host: "localhost"; readonly port: 3000 }
// 常用于定义常量配置
const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE"] as const;
type HttpMethod = (typeof HTTP_METHODS)[number];
// HttpMethod 的类型是 "GET" | "POST" | "PUT" | "DELETE"5. 类型守卫与类型收窄
5.1 typeof 类型守卫
typescript
function padLeft(value: string, padding: string | number): string {
if (typeof padding === "number") {
// 这里 padding 被收窄为 number
return " ".repeat(padding) + value;
}
// 这里 padding 被收窄为 string
return padding + value;
}5.2 instanceof 类型守卫
typescript
class Bird {
fly() {
console.log("Flying...");
}
}
class Fish {
swim() {
console.log("Swimming...");
}
}
function move(animal: Bird | Fish) {
if (animal instanceof Bird) {
animal.fly();
} else {
animal.swim();
}
}5.3 in 操作符
typescript
interface Admin {
name: string;
privileges: string[];
}
interface Employee {
name: string;
startDate: Date;
}
type UnknownEmployee = Admin | Employee;
function printEmployeeInfo(emp: UnknownEmployee) {
console.log(`Name: ${emp.name}`);
if ("privileges" in emp) {
// emp 被收窄为 Admin
console.log(`Privileges: ${emp.privileges.join(", ")}`);
}
if ("startDate" in emp) {
// emp 被收窄为 Employee
console.log(`Start Date: ${emp.startDate.toISOString()}`);
}
}5.4 自定义类型守卫
typescript
// 类型谓词 (Type Predicate)
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
// 返回类型是类型谓词
function isCat(animal: Cat | Dog): animal is Cat {
return (animal as Cat).meow !== undefined;
}
function makeSound(animal: Cat | Dog) {
if (isCat(animal)) {
animal.meow(); // animal 被收窄为 Cat
} else {
animal.bark(); // animal 被收窄为 Dog
}
}
// 断言函数 (Assertion Functions)
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error("Value must be a string");
}
}
function processValue(value: unknown) {
assertIsString(value);
// 这里 value 被收窄为 string
console.log(value.toUpperCase());
}6. 函数类型
6.1 函数类型声明
typescript
// 函数声明
function add(a: number, b: number): number {
return a + b;
}
// 函数表达式
const multiply: (a: number, b: number) => number = (a, b) => a * b;
// 使用类型别名
type MathOperation = (a: number, b: number) => number;
const subtract: MathOperation = (a, b) => a - b;
// 使用接口
interface MathFunc {
(a: number, b: number): number;
}
const divide: MathFunc = (a, b) => a / b;6.2 可选参数与默认参数
typescript
// 可选参数(必须放在必选参数后面)
function greet(name: string, greeting?: string): string {
return `${greeting ?? "Hello"}, ${name}!`;
}
// 默认参数
function createUser(name: string, role: string = "user"): object {
return { name, role };
}
// 剩余参数
function sum(...numbers: number[]): number {
return numbers.reduce((acc, num) => acc + num, 0);
}6.3 函数重载
typescript
// 重载签名
function format(value: string): string;
function format(value: number): string;
function format(value: Date): string;
// 实现签名
function format(value: string | number | Date): string {
if (typeof value === "string") {
return value.trim();
} else if (typeof value === "number") {
return value.toFixed(2);
} else {
return value.toISOString();
}
}
// 调用
format(" hello "); // string 版本
format(3.14159); // number 版本
format(new Date()); // Date 版本
// 更复杂的重载示例
interface User {
id: number;
name: string;
}
function getUser(id: number): User;
function getUser(name: string): User[];
function getUser(idOrName: number | string): User | User[] {
if (typeof idOrName === "number") {
return { id: idOrName, name: "User" };
}
return [{ id: 1, name: idOrName }];
}6.4 this 类型
typescript
// 显式声明 this 类型
interface Calculator {
value: number;
add(n: number): this;
multiply(n: number): this;
}
const calc: Calculator = {
value: 0,
add(this: Calculator, n: number) {
this.value += n;
return this;
},
multiply(this: Calculator, n: number) {
this.value *= n;
return this;
},
};
// 链式调用
calc.add(5).multiply(2).add(3);
console.log(calc.value); // 137. 枚举类型
7.1 数字枚举
typescript
// 默认从 0 开始
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
// 自定义起始值
enum Status {
Pending = 1,
Approved, // 2
Rejected, // 3
}
// 使用枚举
let dir: Direction = Direction.Up;
console.log(dir); // 0
console.log(Direction[0]); // "Up" (反向映射)7.2 字符串枚举
typescript
enum HttpStatus {
OK = "OK",
NotFound = "NOT_FOUND",
InternalError = "INTERNAL_ERROR",
}
// 字符串枚举没有反向映射
console.log(HttpStatus.OK); // "OK"7.3 const 枚举
typescript
// const 枚举在编译时被内联,不会生成额外代码
const enum Colors {
Red = "#FF0000",
Green = "#00FF00",
Blue = "#0000FF",
}
// 编译后直接替换为值
let red = Colors.Red; // 编译为: let red = "#FF0000"7.4 枚举的替代方案
typescript
// 推荐:使用 const 对象 + as const
const Direction = {
Up: "UP",
Down: "DOWN",
Left: "LEFT",
Right: "RIGHT",
} as const;
type Direction = (typeof Direction)[keyof typeof Direction];
// 类型为 "UP" | "DOWN" | "LEFT" | "RIGHT"
// 优点:
// 1. 更好的 tree-shaking
// 2. 与 JavaScript 兼容
// 3. 类型推断更精确8. 类型断言
8.1 基本断言
typescript
// 使用 as 语法(推荐)
let value: unknown = "hello";
let length = (value as string).length;
// 使用尖括号语法(在 JSX 中不可用)
let length2 = (<string>value).length;8.2 非空断言
typescript
// 非空断言操作符 !
function getLength(str: string | null): number {
// 告诉编译器 str 一定不为 null
return str!.length;
}
// 注意:非空断言只是告诉编译器信任你,不会在运行时检查
// 如果 str 实际为 null,会导致运行时错误8.3 双重断言
typescript
// 当直接断言不被允许时,可以使用双重断言
// 但这通常是一个代码坏味道,应该避免
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
// 错误:Cat 和 Dog 没有足够的重叠
// let cat: Cat = dog as Cat;
// 双重断言(不推荐)
let dog = {} as Dog;
let cat = dog as unknown as Cat;9. 实践练习
练习 1:定义用户系统类型
typescript
// 定义用户角色
type UserRole = "admin" | "editor" | "viewer";
// 定义用户状态
type UserStatus = "active" | "inactive" | "banned";
// 定义用户接口
interface User {
id: number;
username: string;
email: string;
role: UserRole;
status: UserStatus;
createdAt: Date;
updatedAt?: Date;
profile?: {
avatar?: string;
bio?: string;
};
}
// 创建用户函数
function createUser(
username: string,
email: string,
role: UserRole = "viewer"
): User {
return {
id: Date.now(),
username,
email,
role,
status: "active",
createdAt: new Date(),
};
}
// 类型守卫:检查是否为管理员
function isAdmin(user: User): boolean {
return user.role === "admin";
}练习 2:API 响应类型
typescript
// 通用 API 响应类型
interface ApiResponse<T> {
code: number;
message: string;
data: T;
timestamp: number;
}
// 分页响应
interface PaginatedData<T> {
items: T[];
total: number;
page: number;
pageSize: number;
hasMore: boolean;
}
// 使用示例
type UserListResponse = ApiResponse<PaginatedData<User>>;
// 模拟 API 调用
async function fetchUsers(
page: number,
pageSize: number
): Promise<UserListResponse> {
// 模拟实现
return {
code: 200,
message: "success",
data: {
items: [],
total: 0,
page,
pageSize,
hasMore: false,
},
timestamp: Date.now(),
};
}10. 小结
本章介绍了 TypeScript 类型系统的基础知识:
- 基本类型:string、number、boolean、null、undefined、void、never、unknown、any
- 对象类型:对象字面量、接口、类型别名
- 联合与交叉类型:灵活组合类型
- 类型推断:让 TypeScript 自动推导类型
- 类型守卫:在运行时收窄类型
- 函数类型:参数、返回值、重载
- 枚举:定义常量集合
- 类型断言:告诉编译器你比它更了解类型
下一章我们将深入学习泛型和高级类型特性。