Skip to content

TypeScript

导航目录

TypeScript 核心概念

什么是 TypeScript?

核心概念

TypeScript 是由微软开发的开源编程语言,具有以下核心特性:

  • JavaScript 超集:完全兼容 JavaScript,支持所有 ES5/ES6+ 语法
  • 静态类型系统:编译时类型检查,提前发现潜在错误
  • 面向对象特性:支持类、接口、泛型等高级特性
  • 工具链支持:提供强大的代码补全、重构和导航功能
  • 大型项目支持:被 VSCode、Angular、Vue3、React 等大型项目采用

安装与编译

bash
# 全局安装 TypeScript

npm install -g typescript

# 编译单个文件

tsc hello.ts

# 初始化配置文件

tsc --init

配置文件深度解析 (tsconfig.json)

核心配置

tsconfig.json 是 TypeScript 项目的核心配置文件,控制编译行为和类型检查规则。

json
{
  "compilerOptions": {
    /* 基本配置 */
    "target": "ES2022",           // 编译目标版本: ES3, ES5, ES2015-ES2022, ESNext
    "module": "ESNext",           // 模块系统: CommonJS, AMD, System, UMD, ES6
    "lib": ["ES2022", "DOM"],     // 包含的库文件
    "outDir": "./dist",           // 输出目录
    "rootDir": "./src",           // 源文件目录

    /* 类型检查 */
    "strict": true,               // 启用所有严格类型检查
    "noImplicitAny": true,        // 禁止隐式 any 类型
    "strictNullChecks": true,     // 严格的 null 检查

    /* 模块解析 */
    "moduleResolution": "node",   // 模块解析策略
    "baseUrl": "./",              // 非绝对路径模块解析基准
    "paths": {                    // 路径映射
      "@/*": ["src/*"]
    },

    /* 高级特性 */
    "experimentalDecorators": true, // 启用装饰器
    "emitDecoratorMetadata": true,  // 生成装饰器元数据
    "esModuleInterop": true         // 改进 CommonJS/ES 模块互操作

    /* 其他重要配置 */
    "declaration": true,          // 生成 .d.ts 类型声明文件
    "sourceMap": true,            // 生成 sourcemap
    "removeComments": false       // 保留注释
  },
  "include": ["src/**/*.ts"],     // 包含文件
  "exclude": ["node_modules"]     // 排除文件
}

基础类型与变量

布尔类型 (boolean)

ts
let isActive: boolean = false;
let hasPermission: boolean = true;

数字类型 (number)

ts
let decimal: number = 6; // 十进制
let hex: number = 0xf00d; // 十六进制
let binary: number = 0b1010; // 二进制
let octal: number = 0o744; // 八进制

字符串类型 (string)

ts
let firstName: string = "John";
let lastName: string = "Doe";
let fullName: string = `${firstName} ${lastName}`; // 模板字符串

数组类型 (array)

ts
// 两种声明方式
let numbers: number[] = [1, 2, 3]; // 元素类型后接[]
let strings: Array<string> = ["a", "b"]; // 使用泛型语法

元组类型 (tuple)

ts
// 定义固定类型和长度的数组
let userInfo: [string, number] = ["Alice", 30];

// 访问元素有正确的类型推断
console.log(userInfo[0].substring(1)); // "lice" - string方法
console.log(userInfo[1].toFixed(2)); // "30.00" - number方法

任意类型 (any)

ts
// 可以赋值为任何类型
let dynamicValue: any = 10;
dynamicValue = "Hello";
dynamicValue = false;

// 实际应用场景:处理第三方库或动态内容
let root: HTMLElement | null = document.getElementById("root");

// 使用非空断言操作符 (!) 告诉编译器值不为 null
root!.style.color = "red";
root!.addEventListener("click", () => {
  console.log("Element clicked");
});

TypeScript 特殊类型深度解析

null 和 undefined 类型

核心概念

  • 非严格模式nullundefined 可以赋值给任何类型
  • 严格模式nullundefined 只能赋值给自身或联合类型
ts
// 非严格模式 (strictNullChecks: false)
let num: number;
num = 1; // ✅ 允许
num = null; // ✅ 允许
num = undefined; // ✅ 允许

// 严格模式 (strictNullChecks: true)
let num: number;
num = 1; // ✅ 允许
num = null; // ❌ 错误:不能将类型"null"分配给类型"number"
num = undefined; // ❌ 错误:不能将类型"undefined"分配给类型"number"

// 显式联合类型声明
let safeNum: number | null | undefined;
safeNum = 1; // ✅ 允许
safeNum = null; // ✅ 允许
safeNum = undefined; // ✅ 允许

最佳实践

  1. 始终启用 strictNullChecks 提高代码安全性
  2. 使用联合类型明确处理可能的空值
  3. 使用可选链操作符 ?. 安全访问嵌套属性
ts
interface User {
  address?: {
    street?: string;
  };
}

const user: User = {};
const street = user.address?.street; // 安全访问,避免运行时错误

void 类型

ts
// 函数无返回值
function logMessage(message: string): void {
  console.log(message);
  // 严格模式下只能返回 undefined
  // return undefined; // ✅ 允许
  // return null;     // ❌ 错误 (strictNullChecks: true)
}

// void 变量声明
let unusable: void = undefined; // ✅ 允许
unusable = null; // ❌ 错误 (strictNullChecks: true)

// 实际应用:事件处理
button.addEventListener("click", (): void => {
  console.log("Button clicked");
});

never 类型

ts
// 抛出异常的函数
function throwError(message: string): never {
  throw new Error(message);
}

// 永不返回的函数
function infiniteLoop(): never {
  while (true) {
    // 无限循环
  }
}

// 类型收窄的 never
type AllStrings = string | number;
function handleValue(val: AllStrings) {
  if (typeof val === "string") {
    // 处理字符串
  } else if (typeof val === "number") {
    // 处理数字
  } else {
    // 此分支 val 为 never 类型
    const exhaustiveCheck: never = val;
  }
}

never 与 void 的区别

特性voidnever
表示含义没有返回值永不出现的值
可赋值内容undefined (严格模式)无任何值可赋值
函数返回正常返回但无值函数无法正常完成执行
变量声明可声明为 undefined不能声明变量
类型收窄不参与类型收窄用于穷尽检查
使用场景无返回值的函数抛出异常/无限循环的函数

Symbol 类型

ts
// 创建唯一标识
const userId = Symbol("userID");
const sessionId = Symbol("sessionID");

// 作为对象属性
const user = {
  [userId]: "12345",
  name: "John Doe",
  [sessionId]: "abcde",
};

console.log(user[userId]); // '12345'

// 全局 Symbol 注册表
const globalSymbol = Symbol.for("app.global");
const sameGlobalSymbol = Symbol.for("app.global");

console.log(globalSymbol === sameGlobalSymbol); // true
console.log(Symbol.keyFor(globalSymbol)); // 'app.global'

// 内置 Symbols - 实现可迭代对象
const customCollection = {
  items: [10, 20, 30],
  [Symbol.iterator]: function* () {
    for (const item of this.items) {
      yield item;
    }
  },
};

for (const num of customCollection) {
  console.log(num); // 10, 20, 30
}

Symbol 最佳实践

  1. 创建唯一属性键避免命名冲突
  2. 使用 Symbol.for() 在全局注册表中共享 Symbols
  3. 实现内置 Symbols 如 Symbol.iterator 创建自定义迭代器
  4. 使用 Object.getOwnPropertySymbols() 获取对象的所有 Symbol 属性

枚举类型

普通枚举

ts
// 基本枚举(数字枚举)
enum Direction {
  Up, // 0 (默认从0开始)
  Down, // 1
  Left, // 2
  Right, // 3
}

console.log(Direction.Up); // 0
console.log(Direction[0]); // "Up" (反向映射)

// 指定初始值
enum StatusCodes {
  Success = 200, // 200
  NotFound = 404, // 404
  Error = 500, // 500
}

console.log(StatusCodes.Success); // 200

// 混合枚举
enum MixedEnum {
  // 字符串成员
  Admin = "ADMIN",
  User = "USER",

  // 数字成员 (可以自动递增)
  Read = 1, // 1
  Write, // 2
  Execute, // 3
}

console.log(MixedEnum.Admin); // "ADMIN"
console.log(MixedEnum.Write); // 2

// 计算成员示例
enum FileAccess {
  // 常量成员
  None,
  Read = 1 << 1, // 2 (位运算)
  Write = 1 << 2, // 4
  ReadWrite = Read | Write, // 6 (组合权限)

  // 字符串计算成员 (TypeScript 5.0+)
  Description = "Level: " + ReadWrite.toString(), // "Level: 6"
}

console.log(FileAccess.ReadWrite); // 6
console.log(FileAccess.Description); // "Level: 6"

常数枚举 (const enums)

ts
// 常数枚举 - 编译时会被内联
const enum LogLevel {
  Error = 1,
  Warn,
  Info,
  Debug,
}

// 使用示例 - 编译后会被替换为字面量
let currentLevel = LogLevel.Error;

function log(message: string, level: LogLevel) {
  if (level === LogLevel.Error) {
    console.error(message);
  } else if (level === LogLevel.Warn) {
    console.warn(message);
  }
  // ...
}

log("Critical issue!", LogLevel.Error);

// 编译后的JavaScript代码:
// let currentLevel = 1;
// function log(message, level) {
//   if (level === 1) { ... }
// }
// log("Critical issue!", 1);

枚举最佳实践

  1. 优先使用常量枚举

    ts
    // 减少运行时开销
    const enum UserRole {
      Admin = "ADMIN",
      Editor = "EDITOR",
      Viewer = "VIEWER",
    }
  2. 避免数字枚举的陷阱

    ts
    // 不良实践 - 易出错的数字枚举
    enum BadStatus {
      Active = 1,
      Pending, // 2 - 如果插入新值会破坏顺序
      Completed, // 3
    }
    
    // 良好实践 - 显式赋值
    enum GoodStatus {
      Active = 10,
      Pending = 20,
      Completed = 30,
    }
  3. 使用字符串枚举提高可读性

    ts
    // 更易调试和维护
    enum MediaType {
      JPEG = "image/jpeg",
      PNG = "image/png",
      PDF = "application/pdf",
    }
    
    function handleFile(type: MediaType) {
      // 直接使用可读的字符串值
    }

枚举编译行为对比

特性普通枚举常量枚举
运行时存在是 (生成真实对象)否 (编译时内联)
支持计算成员否 (仅允许常量表达式)
反向映射是 (数字成员)
适用场景需要运行时访问的枚举仅编译时使用的枚举
输出大小增加代码体积无额外代码体积
性能影响有运行时开销无运行时开销

现代替代方案 (TypeScript 5.0+)

ts
// 使用 as const 实现枚举类似功能
const LogLevel = {
  Error: 0,
  Warn: 1,
  Info: 2,
  Debug: 3,
} as const;

type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];

// 使用示例
function log(message: string, level: LogLevel) {
  // ...
}

log("Info message", LogLevel.Info);

何时使用枚举 vs 对象常量

  • 需要反向映射 → 选择枚举
  • 需要数字自动递增 → 选择枚举
  • 需要最小化运行时开销 → 选择常量枚举或对象常量
  • 需要严格的类型安全 → 选择对象常量 + as const

枚举是 TypeScript 强大的特性之一,合理使用可以:

  1. 提高代码可读性和可维护性
  2. 创建有意义的命名常量集合
  3. 简化复杂状态管理
  4. 通过类型检查减少错误

类型自动推断

ts
// 未初始化时推断为 any 类型
let uninitialized;
uninitialized = 10; // ✅ 允许
uninitialized = []; // ✅ 允许

// 初始化时推断具体类型
let initialized = 10; // 推断为 number 类型
initialized = "1"; // ❌ 错误:不能将类型 "string" 分配给类型 "number"

联合类型

ts
// 定义联合类型
let value: string | number;

// 非严格模式 (strictNullChecks: false)
console.log(value.toString()); // ✅ 访问共有方法 (string 和 number 都有 toString)

value = 3;
console.log(value.toFixed(2)); // ✅ 访问 number 方法

value = "zhufeng";
console.log(value.length); // ✅ 访问 string 属性

// 严格模式 (strictNullChecks: true)
console.log(value.toString()); // ❌ 错误:变量 'value' 在赋值前被使用

联合类型与交叉类型对比

基本用法

ts
interface A {
  name: string;
}

interface B {
  age: number;
}

// 联合类型:只需满足 A 或 B 之一
type UnionAB = A | B;
const union1: UnionAB = { name: "Alice" }; // ✅ 满足 A
const union2: UnionAB = { age: 30 }; // ✅ 满足 B

// 交叉类型:必须同时满足 A 和 B
type IntersectionAB = A & B;
const intersection: IntersectionAB = {
  name: "Alice",
  age: 30,
}; // ✅ 同时满足 A 和 B

// 不可能的类型
type Impossible = string & number; // 推断为 never

对象类型的交叉类型

ts
type TypeA = {
  a: string;
  b: {
    name: string;
  };
};

type TypeB = {
  a: number; // 与 TypeA 的 a 冲突
  b: {
    age: number;
  };
};

type TypeC = TypeA & TypeB;

// 冲突属性成为 never
type PropA = TypeC["a"]; // never

// 嵌套属性合并
type PropB = TypeC["b"]; // { name: string; age: number; }

// 实际使用
const validObj: TypeC["b"] = {
  name: "Alice",
  age: 30,
};

类型断言技巧

基础断言

ts
let value: string | number;

// 非严格模式
console.log((value as string).length); // ✅
console.log((value as number).toFixed(2)); // ✅

// 严格模式 (未赋值时报错)
console.log((value as string).length); // ❌ 错误:变量 'value' 在赋值前被使用

value = "test";
console.log((value as string).length); // ✅ 4

双重断言

ts
interface UserInfo {
  name: string;
  age: number;
}

// 处理 API 响应时的双重断言
fetch("https://api.example.com/user")
  .then((response) => response.json())
  .then((data) => {
    // 先断言为 unknown,再断言为目标类型
    const user = data as unknown as UserInfo;
    console.log(user.name, user.age);
  });

// 处理第三方库返回
const unsafeData: any = getExternalData();
const safeData = unsafeData as unknown as UserInfo;

字面量类型与类型字面量

字面量类型

ts
// 特定值作为类型
const directionUp: "Up" = "Up";
const directionDown: "Down" = "Down";

// 联合字面量类型
type Direction = "Up" | "Down" | "Left" | "Right";

function move(direction: Direction) {
  console.log(`Moving ${direction}`);
}

move("Up"); // ✅
move("North"); // ❌ 错误:参数类型不匹配

// 实际应用:Redux action types
type ActionType = "ADD_TODO" | "REMOVE_TODO" | "TOGGLE_TODO";

function dispatch(action: ActionType) {
  // ...
}

类型字面量

ts
// 内联类型定义
type Point = {
  x: number;
  y: number;
};

const p: Point = { x: 10, y: 20 };

// 函数类型字面量
type Greeter = (name: string) => string;
const greet: Greeter = (name) => `Hello, ${name}!`;

// 复杂类型组合
type UserProfile = {
  id: number;
  name: string;
  preferences: {
    theme: "light" | "dark";
    notifications: boolean;
  };
};

最佳实践总结

  1. 优先使用类型推断

    ts
    // 良好:利用类型推断
    const items = [1, 2, 3]; // 推断为 number[]
    
    // 避免冗余类型声明
    const items: number[] = [1, 2, 3];
  2. 合理使用联合类型

    ts
    // 处理多种可能类型
    function formatInput(input: string | number) {
      return typeof input === "string" ? input.trim() : input.toFixed(2);
    }
  3. 谨慎使用类型断言

    ts
    // 优先使用类型守卫
    if (typeof value === "string") {
      console.log(value.length);
    }
    
    // 必要时使用双重断言
    const safeValue = unsafeValue as unknown as TargetType;
  4. 利用字面量类型增强类型安全

    ts
    // 精确的状态管理
    type LoadingState = "idle" | "loading" | "succeeded" | "failed";
    
    let currentState: LoadingState = "idle";
  5. 使用类型别名简化复杂类型

    ts
    // 简化复杂类型
    type ApiResponse<T> = {
      data: T;
      status: number;
      timestamp: Date;
    };
    
    type UserResponse = ApiResponse<{ id: number; name: string }>;

函数参数类型

ts
function hello(name: string): void {
  console.log("hello", name);
}
hello("hl");

函数类型约束

ts
type typeAdd = (x: number, y: number) => number;
let add: typeAdd = (x, y) => {
  return x + y;
};
add(1, 2);

函数参数处理

可选参数

ts
function printUser(name: string, age?: number): void {
  console.log(`Name: ${name}, Age: ${age || "unknown"}`);
}

printUser("Alice"); // Name: Alice, Age: unknown
printUser("Bob", 30); // Name: Bob, Age: 30

默认参数

ts
function request(url: string, method: string = "GET") {
  console.log(`Sending ${method} request to ${url}`);
}

request("/api/users"); // Sending GET request to /api/users
request("/api/posts", "POST"); // Sending POST request to /api/posts

剩余参数

ts
function calculateAverage(...scores: number[]): number {
  const total = scores.reduce((sum, score) => sum + score, 0);
  return total / scores.length;
}

console.log(calculateAverage(85, 90, 78)); // 84.333...

函数重载

ts
// 重载签名
function formatInput(input: string): string;
function formatInput(input: number): string;

// 实现签名
function formatInput(input: any): string {
  if (typeof input === "string") {
    return input.trim().toUpperCase();
  } else if (typeof input === "number") {
    return input.toFixed(2);
  }
  return "";
}

console.log(formatInput("  hello  ")); // "HELLO"
console.log(formatInput(3.1415926)); // "3.14"

类特性详解

参数属性

ts
// 传统方式
class User {
  public name: string;
  public age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

// 使用参数属性(简写)
class UserShort {
  constructor(public name: string, public age: number) {}
}

const user1 = new User("Alice", 30);
const user2 = new UserShort("Bob", 25);

访问修饰符

ts
class Account {
  // 公共属性(默认)
  public id: number;

  // 受保护属性(仅限类和子类访问)
  protected balance: number;

  // 私有属性(仅限类内部访问)
  private pin: string;

  // 只读属性(初始化后不可修改)
  readonly createdAt: Date;

  constructor(id: number, pin: string) {
    this.id = id;
    this.balance = 0;
    this.pin = pin;
    this.createdAt = new Date();
  }

  // 公共方法
  public deposit(amount: number): void {
    this.balance += amount;
  }

  // 受保护方法
  protected verifyPin(pin: string): boolean {
    return this.pin === pin;
  }
}

class SavingsAccount extends Account {
  withdraw(amount: number, pin: string): boolean {
    if (this.verifyPin(pin)) {
      this.balance -= amount;
      return true;
    }
    return false;
  }
}

const account = new SavingsAccount(123, "1234");
account.deposit(1000);
console.log(account.withdraw(500, "1234")); // true

静态成员

ts
class MathUtils {
  // 静态属性
  static PI = 3.1415926535;

  // 静态方法
  static circleArea(radius: number): number {
    return this.PI * radius ** 2;
  }
}

console.log(MathUtils.PI); // 3.1415926535
console.log(MathUtils.circleArea(5)); // 78.5398163375

继承与抽象类

ts
// 抽象类
abstract class Animal {
  constructor(public name: string) {}

  // 抽象方法(必须由子类实现)
  abstract makeSound(): void;

  // 普通方法
  move(distance: number = 0): void {
    console.log(`${this.name} moved ${distance}m`);
  }
}

// 具体子类
class Dog extends Animal {
  constructor(name: string) {
    super(name);
  }

  makeSound(): void {
    console.log("Woof! Woof!");
  }

  // 扩展方法
  fetch(item: string): void {
    console.log(`${this.name} fetched the ${item}`);
  }
}

const dog = new Dog("Buddy");
dog.makeSound(); // "Woof! Woof!"
dog.move(10); // "Buddy moved 10m"
dog.fetch("ball"); // "Buddy fetched the ball"

接口系统

类实现接口

ts
interface Loggable {
  log(message: string): void;
}

interface Serializable {
  serialize(): string;
}

// 实现多个接口
class Logger implements Loggable, Serializable {
  constructor(private name: string) {}

  log(message: string): void {
    console.log(`[${this.name}] ${message}`);
  }

  serialize(): string {
    return JSON.stringify({ name: this.name });
  }
}

const logger = new Logger("App");
logger.log("Starting application"); // [App] Starting application
console.log(logger.serialize()); // {"name":"App"}

接口继承

ts
interface Shape {
  color: string;
}

interface Circle extends Shape {
  radius: number;
  getArea(): number;
}

const myCircle: Circle = {
  color: "red",
  radius: 10,
  getArea() {
    return Math.PI * this.radius ** 2;
  },
};

console.log(myCircle.getArea()); // 314.159...

函数类型接口

ts
interface Transformer {
  (input: string): string;
}

const toUpper: Transformer = (str) => str.toUpperCase();
const reverse: Transformer = (str) => str.split("").reverse().join("");

console.log(toUpper("hello")); // "HELLO"
console.log(reverse("world")); // "dlrow"

可索引接口

ts
// 数组约束
interface StringArray {
  [index: number]: string;
}

const fruits: StringArray = ["Apple", "Banana", "Cherry"];
console.log(fruits[1]); // "Banana"

// 对象约束
interface NumberDictionary {
  [key: string]: number;
  length: number; // 必须匹配索引签名
}

const scores: NumberDictionary = {
  math: 90,
  science: 85,
  length: 2,
};

装饰器应用

类装饰器

ts
// 装饰器工厂
function LogClass(prefix: string) {
  // 类装饰器
  return function (constructor: Function) {
    console.log(`${prefix}: ${constructor.name} class initialized`);
  };
}

@LogClass("[LOGGER]")
class DatabaseService {
  constructor() {
    console.log("Database service created");
  }
}

// 控制台输出:
// [LOGGER]: DatabaseService class initialized
// Database service created

抽象类 vs 接口对比

特性抽象类接口
实例化❌ 不能实例化❌ 不能实例化
方法实现✅ 可包含具体实现❌ 仅声明不实现
属性初始化✅ 可初始化属性❌ 仅声明属性
访问修饰符✅ 支持 public/protected/private❌ 所有成员隐式 public
继承单继承(extends)多实现(implements)
构造函数✅ 可以有构造函数❌ 不能有构造函数
使用场景提供基础实现,要求子类完善定义契约,要求类遵循规范

最佳实践总结

  1. 函数设计

    • 使用可选参数处理可选值
    • 默认参数简化调用
    • 剩余参数处理可变参数
    • 函数重载提供类型友好的多态接口
  2. 类设计原则

    ts
    // 良好实践:使用参数属性简化代码
    class Point {
      constructor(public x: number, public y: number) {}
    }
    
    // 访问控制:合理使用 public/protected/private
    class SecureData {
      private secret: string;
    
      constructor(secret: string) {
        this.secret = secret;
      }
    
      public getMaskedSecret(): string {
        return this.secret.replace(/./g, "*");
      }
    }
  3. 接口应用

    ts
    // 定义服务契约
    interface PaymentProcessor {
      process(amount: number): boolean;
      refund(transactionId: string): boolean;
    }
    
    // 实现支付服务
    class StripeProcessor implements PaymentProcessor {
      process(amount: number) {
        /* ... */
      }
      refund(id: string) {
        /* ... */
      }
    }
  4. 抽象类使用场景

    ts
    // 提供基础实现
    abstract class BaseAPI {
      constructor(protected baseUrl: string) {}
    
      abstract fetchData(endpoint: string): Promise<any>;
    
      protected buildUrl(endpoint: string): string {
        return `${this.baseUrl}/${endpoint}`;
      }
    }
    
    class UsersAPI extends BaseAPI {
      async fetchData(endpoint: string) {
        const url = this.buildUrl(endpoint);
        return fetch(url).then((res) => res.json());
      }
    }

泛型

泛型基础概念

核心概念

泛型(Generics)允许在定义函数、接口或类时不预先指定具体类型,而是在使用时再指定类型,提供代码复用性和类型安全性。

ts
// 泛型函数
function createArray<T>(length: number, value: T): T[] {
  const result: T[] = [];
  for (let i = 0; i < length; i++) {
    result.push(value);
  }
  return result;
}

// 使用示例
const stringArray = createArray<string>(3, "x"); // string[]
const numberArray = createArray(3, 1); // number[] (类型推断)

泛型类

ts
class Collection<T> {
  private items: T[] = [];

  add(item: T): void {
    this.items.push(item);
  }

  getFirst(): T | undefined {
    return this.items[0];
  }

  // 泛型约束方法
  filterByType<U extends T>(type: new (...args: any[]) => U): U[] {
    return this.items.filter((item): item is U => item instanceof type);
  }
}

// 使用示例
const numberCollection = new Collection<number>();
numberCollection.add(1);
numberCollection.add(2);
console.log(numberCollection.getFirst()); // 1

class User {
  name = "";
}
const userCollection = new Collection<User>();
userCollection.add(new User());
const filtered = userCollection.filterByType(User); // User[]

泛型接口

ts
// 函数类型接口
interface Comparator<T> {
  (a: T, b: T): number;
}

// 对象类型接口
interface Repository<T> {
  getById(id: string): T | undefined;
  save(entity: T): void;
}

// 使用示例
const numberComparator: Comparator<number> = (a, b) => a - b;
console.log([10, 5, 8].sort(numberComparator)); // [5, 8, 10]

class UserRepo implements Repository<User> {
  private users: User[] = [];

  getById(id: string) {
    return this.users.find((u) => u.id === id);
  }

  save(user: User) {
    this.users.push(user);
  }
}

高级泛型技巧

多类型参数

ts
function merge<A, B>(a: A, b: B): A & B {
  return { ...a, ...b };
}

const user = { name: "Alice" };
const account = { balance: 100 };
const merged = merge(user, account); // { name: string; balance: number }

默认泛型类型

ts
function createApiClient<T = object>(
  baseUrl: string
): {
  get: (path: string) => Promise<T>;
  post: (path: string, data: any) => Promise<T>;
} {
  return {
    get: async (path) => (await fetch(`${baseUrl}${path}`)).json(),
    post: async (path, data) =>
      (
        await fetch(`${baseUrl}${path}`, {
          method: "POST",
          body: JSON.stringify(data),
        })
      ).json(),
  };
}

// 使用默认类型
const api = createApiClient("/api");
const data = await api.get("/users"); // object

// 指定类型
const userApi = createApiClient<User>("/api/users");
const user = await userApi.get("/123"); // User

泛型约束

ts
interface Identifiable {
  id: string;
}

// 约束泛型必须实现Identifiable接口
function logId<T extends Identifiable>(entity: T): void {
  console.log(entity.id);
}

// 约束泛型为构造函数
function createInstance<T>(ctor: new () => T): T {
  return new ctor();
}

泛型类型别名

ts
// 响应数据格式
type ApiResponse<T> = {
  data: T;
  status: number;
  timestamp: Date;
};

// 分页数据
type Paginated<T> = {
  items: T[];
  total: number;
  page: number;
  perPage: number;
};

// 使用示例
const userResponse: ApiResponse<User> = {
  data: { id: "1", name: "Alice" },
  status: 200,
  timestamp: new Date(),
};

const paginatedUsers: Paginated<User> = {
  items: [{ id: "1", name: "Alice" }],
  total: 1,
  page: 1,
  perPage: 10,
};

泛型接口 vs 泛型类型别名

特性泛型接口泛型类型别名
创建新类型名✅ 创建新类型名❌ 只是别名
扩展 (extends)✅ 支持❌ 不支持
实现 (implements)✅ 类可实现接口❌ 类不能实现类型别名
联合类型❌ 不适合联合类型✅ 适合联合/交叉类型
声明合并✅ 支持多声明合并❌ 不支持
错误信息显示✅ 显示接口名❌ 显示原始类型结构
递归类型❌ 有限支持✅ 完整支持

最佳实践指南

  1. 优先使用接口定义对象结构

    ts
    // 良好实践:使用接口
    interface Response<T> {
      data: T;
      status: number;
    }
    
    // 避免:类型别名定义对象
    type Response<T> = {
      data: T;
      status: number;
    };
  2. 使用类型别名处理复杂类型

    ts
    // 联合类型
    type Result<T> = Success<T> | Error;
    
    // 元组类型
    type Point3D = [number, number, number];
  3. 合理使用泛型约束

    ts
    // 约束键名存在
    function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
      return obj[key];
    }
    
    const user = { name: "Alice", age: 30 };
    getProperty(user, "name"); // ✅
    getProperty(user, "email"); // ❌ 错误
  4. 避免过度泛型化

    ts
    // 不良实践:过度泛型化
    function parse<T>(input: string): T {
      return JSON.parse(input);
    }
    
    // 良好实践:使用类型断言
    function parse<T>(input: string): T {
      return JSON.parse(input) as T;
    }

泛型在 React 中的应用

tsx
// 泛型组件
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

function GenericList<T>({ items, renderItem }: ListProps<T>) {
  return <div>{items.map(renderItem)}</div>;
}

// 使用示例
<GenericList
  items={[
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
  ]}
  renderItem={(user) => <div key={user.id}>{user.name}</div>}
/>;

// 自定义hooks中的泛型
function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

// 使用示例
const [user, setUser] = useLocalStorage<User>("user", { name: "" });

TypeScript 类型兼容性深度解析

接口兼容性规则

ts
interface Animal {
  name: string;
  age: number;
}

interface Person {
  name: string;
  age: number;
  gender: number;
}

// 函数参数兼容性检查
function getName(animal: Animal): string {
  return animal.name;
}

const p: Person = {
  name: "zhufeng",
  age: 10,
  gender: 0,
};

getName(p); // ✅ 允许:Person 满足 Animal 结构

// 直接赋值类型检查
const a: Animal = {
  name: "zhufeng",
  age: 10,
  gender: 0, // ❌ 错误:Animal 类型不存在 gender 属性
};

基本类型兼容性

ts
// 联合类型兼容性
let num: string | number;
const str: string = "zhufeng";
num = str; // ✅ 允许:string 是 string | number 的子类型

// 结构兼容性
let num2: { toString(): string };
const str2: string = "jiagou";
num2 = str2; // ✅ 允许:string 类型具有 toString 方法

类兼容性(结构类型系统)

ts
class Animal {
  name: string = "";
}

class Bird extends Animal {
  swing: number = 2;
}

// 子类实例可赋值给父类变量
let a: Animal;
a = new Bird(); // ✅ 允许:Bird 包含 Animal 的所有属性

// 父类实例不可赋值给子类变量(缺少子类特有属性)
let b: Bird;
b = new Animal(); // ❌ 错误:Animal 缺少 swing 属性

// 无继承关系但结构相同的类可相互赋值
class Car {
  name: string = "";
}
const animal: Animal = new Car(); // ✅ 允许:结构相同

函数兼容性规则

参数兼容性

ts
type SumFunc = (a: number, b: number) => number;
let sum: SumFunc;

// 参数个数兼容性
const f1 = (a: number, b: number) => a + b; // ✅
const f2 = (a: number) => a; // ✅
const f3 = () => 0; // ✅
const f4 = (a: number, b: number, c: number) => a + b + c; // ❌

sum = f1;
sum = f2;
sum = f3;
sum = f4; // 错误:参数个数超过目标类型

返回值兼容性

ts
type GetPerson = () => { name: string; age: number };
let getPerson: GetPerson;

// 返回值类型兼容性
const g1 = () => ({ name: "zhufeng", age: 10 }); // ✅
const g2 = () => ({ name: "zhufeng", age: 10, gender: "男" }); // ✅
const g3 = () => ({ name: "zhufeng" }); // ❌

getPerson = g1;
getPerson = g2;
getPerson = g3; // 错误:缺少 age 属性

泛型兼容性规则

ts
// 1. 空接口(未使用泛型参数)相互兼容
interface Empty<T> {}
let x: Empty<string>;
let y: Empty<number>;
x = y; // ✅ 允许

// 2. 使用泛型参数的接口不兼容
interface NotEmpty<T> {
  data: T;
}
let x1: NotEmpty<string>;
let y1: NotEmpty<number>;
x1 = y1; // ❌ 错误:类型参数不同

// 等价于以下非泛型接口:
interface NotEmptyString {
  data: string;
}
interface NotEmptyNumber {
  data: number;
}
let xx2!: NotEmptyString;
let yy2!: NotEmptyNumber;
xx2 = yy2; // ❌ 错误:类型不兼容

枚举兼容性规则

ts
enum Colors {
  Red,
  Yellow,
}
enum Directions {
  Up,
  Down,
}

let c: Colors;
c = Colors.Red; // ✅
c = 1; // ✅ 数字兼容枚举
c = "1"; // ❌ 字符串不兼容

let n: number;
n = 1; // ✅
n = Colors.Red; // ✅ 枚举兼容数字

// 不同枚举类型不兼容
let d: Directions;
d = Colors.Red; // ❌ 错误:Colors 与 Directions 不兼容

类型兼容性总结表

| 类型 | 兼容规则 | 示例 | | ---------------- | ------------------------------------ | ------------------------------------------ | ---------- | | 接口 | 源类型必须包含目标类型的所有属性 | PersonAnimal ✅ | | 基本类型 | 子类型可赋值给父类型 | stringstring | number ✅ | | 类 | 结构相同即兼容(名称无关) | CarAnimal ✅ | | 函数参数 | 目标参数个数 ≤ 源参数个数 | (a) => ...(a,b) => ... ✅ | | 函数返回值 | 目标返回值类型必须包含源返回值类型 | {name,age}{name,age,gender} ✅ | | 泛型(空接口) | 未使用泛型参数的接口相互兼容 | Empty<string>Empty<number> ✅ | | 泛型(非空接口) | 类型参数相同时才兼容 | NotEmpty<string>NotEmpty<number> ❌ | | 枚举 | 枚举与数字相互兼容,不同枚举互不兼容 | Colors.Rednumber ✅ |

TypeScript 类型保护深度解析

类型保护核心概念

核心概念

类型保护是 TypeScript 在编译时通过特定表达式确保作用域内变量类型的机制,主要通过以下方式实现:

  • typeof 操作符:用于基本类型检查
  • instanceof 操作符:用于类实例检查
  • in 操作符:用于属性存在性检查
  • 类型谓词:自定义类型保护函数
  • 可辨识联合类型:通过共同字段区分类型

typeof 类型保护

ts
function processValue(value: string | number | boolean) {
  if (typeof value === "string") {
    // 此作用域内 value 被识别为 string
    console.log(value.toUpperCase());
  } else if (typeof value === "number") {
    // 此作用域内 value 被识别为 number
    console.log(value.toFixed(2));
  } else {
    // 此作用域内 value 被识别为 boolean
    console.log(!value);
  }
}

instanceof 类型保护

ts
class Animal {
  name!: string;
}

class Bird extends Animal {
  swing!: number;
}

function describeAnimal(animal: Animal) {
  if (animal instanceof Bird) {
    // 此作用域内 animal 被识别为 Bird
    console.log(`Bird with ${animal.swing} wings`);
  } else {
    // 此作用域内 animal 被识别为 Animal
    console.log(`Animal named ${animal.name}`);
  }
}

null/undefined 保护

ts
function safeAccess(value: string | null | undefined) {
  // 显式 null/undefined 检查
  if (value == null) {
    return "";
  }

  // 此作用域内 value 被识别为 string
  return value.trim();
}

function logValue(value: string | null) {
  // 链判断运算符
  console.log(value?.toUpperCase());

  // 非空断言 (谨慎使用)
  console.log(value!.length);
}

链判断运算符(?.)

ts
interface UserProfile {
  address?: {
    street?: {
      name: string;
    };
  };
}

const getStreetName = (user: UserProfile): string => {
  return user.address?.street?.name || "Unknown Street";
};

const callIfExists = (fn?: () => void) => {
  fn?.(); // 安全调用函数
};

可辨识联合类型

ts
// 通过共同字段区分类型
type NetworkResponse =
  | { status: "loading"; progress: number }
  | { status: "success"; data: string }
  | { status: "error"; code: number };

function handleResponse(response: NetworkResponse) {
  switch (response.status) {
    case "loading":
      console.log(`Progress: ${response.progress}%`);
      break;
    case "success":
      console.log(`Data: ${response.data}`);
      break;
    case "error":
      console.error(`Error code: ${response.code}`);
      break;
  }
}

in 操作符类型保护

ts
interface Bird {
  fly(): void;
  swing: number;
}

interface Fish {
  swim(): void;
  fin: number;
}

function move(animal: Bird | Fish) {
  if ("swing" in animal) {
    // 此作用域内 animal 被识别为 Bird
    animal.fly();
  } else {
    // 此作用域内 animal 被识别为 Fish
    animal.swim();
  }
}

自定义类型保护(类型谓词)

ts
// 类型谓词函数
function isBird(animal: Bird | Fish): animal is Bird {
  return "fly" in animal && typeof animal.fly === "function";
}

function handleAnimal(animal: Bird | Fish) {
  if (isBird(animal)) {
    // 此作用域内 animal 被识别为 Bird
    animal.fly();
  } else {
    // 此作用域内 animal 被识别为 Fish
    animal.swim();
  }
}

类型保护综合应用

ts
type PaymentMethod =
  | { type: "credit"; cardNumber: string; expiry: string }
  | { type: "paypal"; email: string }
  | { type: "crypto"; walletAddress: string };

function processPayment(method: PaymentMethod) {
  if (method.type === "credit") {
    console.log(`Processing credit card: ${method.cardNumber}`);
  } else if (method.type === "paypal") {
    console.log(`Processing PayPal: ${method.email}`);
  } else {
    console.log(`Processing crypto: ${method.walletAddress}`);
  }
}

// 复杂场景:嵌套类型保护
interface Order {
  id: string;
  payment?: PaymentMethod;
  customer?: {
    name: string;
    contact?: {
      email: string;
      phone?: string;
    };
  };
}

function sendConfirmation(order: Order) {
  // 链判断 + 类型保护
  const email = order.customer?.contact?.email;

  if (email) {
    console.log(`Sending confirmation to ${email}`);
  } else if (order.payment?.type === "credit") {
    console.log("Sending SMS confirmation");
  } else {
    console.log("Cannot send confirmation");
  }
}

类型保护机制对比表

类型保护方式适用场景优点注意事项
typeof基本类型区分简单直观,性能好不能区分自定义类型
instanceof类实例区分面向对象场景最佳实践不能用于接口
in 操作符对象属性检查适用于接口和对象类型可能误判相似结构
类型谓词复杂类型判断灵活性高,可自定义需手动实现逻辑
可辨识联合类型多类型区分类型安全,代码清晰需要共同的判别字段
链判断运算符嵌套属性访问简洁安全,避免空值错误仅支持访问,不支持赋值

TypeScript 最佳实践总结

代码组织与结构

  1. 合理使用类型定义

    • 为复杂数据结构创建接口或类型别名
    • 优先使用接口定义对象结构
    • 使用类型别名处理联合类型和交叉类型
  2. 类型安全

    • 启用严格模式(strict: true
    • 避免使用 any 类型
    • 使用联合类型明确表示可能的类型
    • 合理使用类型断言,避免过度断言
  3. 性能优化

    • 使用常量枚举减少运行时开销
    • 避免过度泛型化
    • 合理使用类型守卫减少运行时检查
  4. 代码可读性

    • 使用有意义的类型名称
    • 添加类型注释说明复杂类型
    • 保持类型定义与实现分离

实际应用场景

  1. React 应用

    • 使用泛型组件提高复用性
    • 为 props 和 state 定义明确的类型
    • 使用自定义 hooks 时添加泛型支持
  2. Node.js 应用

    • 为 API 响应和请求定义类型
    • 使用类型保护处理错误边界
    • 为配置对象创建严格的类型定义
  3. 大型项目

    • 统一类型定义,创建类型库
    • 使用命名空间组织类型
    • 为第三方库添加类型声明

常见问题与解决方案

  1. 类型推断失败

    • 显式添加类型注释
    • 使用类型断言
    • 检查类型定义是否正确
  2. 类型冲突

    • 检查接口和类型别名的命名
    • 使用交叉类型合并类型
    • 避免重复定义类型
  3. 性能问题

    • 减少大型类型的使用
    • 使用常量枚举
    • 优化类型守卫逻辑
  4. 编译错误

    • 检查 tsconfig.json 配置
    • 确保所有依赖都有类型声明
    • 使用 // @ts-ignore 临时忽略错误(谨慎使用)

总结

核心要点

  • TypeScript 是 JavaScript 的超集,提供静态类型检查和高级语言特性
  • 类型系统是 TypeScript 的核心,包括基础类型、联合类型、交叉类型、泛型等
  • 接口和类提供了面向对象编程的能力,支持继承、实现和抽象
  • 类型保护机制确保运行时类型安全,避免类型错误
  • 最佳实践包括启用严格模式、避免使用 any、合理使用泛型和类型守卫

TypeScript 为 JavaScript 带来了静态类型检查的能力,显著提高了代码的可维护性和可靠性。通过合理使用 TypeScript 的类型系统、接口、泛型等特性,可以编写更加健壮、可预测的代码。随着 TypeScript 在前端和后端的广泛应用,掌握其核心概念和最佳实践已成为现代 JavaScript 开发者的必备技能。