简体中文 繁體中文 English Deutsch 한국 사람 بالعربية TÜRKÇE português คนไทย Français Japanese

站内搜索

搜索

活动公告

通知:为庆祝网站一周年,将在5.1日与5.2日开放注册,具体信息请见后续详细公告
04-22 00:04
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

TypeScript类型安全代码编写从基础到高级打造无bug应用程序的完整指南

SunJu_FaceMall

3万

主题

1158

科技点

3万

积分

白金月票

碾压王

积分
32796

立华奏

发表于 2025-10-2 15:50:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

TypeScript作为JavaScript的超集,通过添加静态类型系统,为开发者提供了强大的工具来编写更安全、更可维护的代码。在大型应用程序开发中,类型安全不仅能减少运行时错误,还能提高代码的可读性和可维护性。本文将深入探讨TypeScript的类型系统,从基础概念到高级技巧,帮助你掌握编写无bug应用程序的技能。

TypeScript基础类型系统

基本类型

TypeScript提供了一系列基本类型,这些是构建复杂类型系统的基础:
  1. // 字符串类型
  2. let name: string = "Alice";
  3. // 数字类型
  4. let age: number = 30;
  5. // 布尔类型
  6. let isActive: boolean = true;
  7. // null和undefined
  8. let nullValue: null = null;
  9. let undefinedValue: undefined = undefined;
  10. // symbol类型
  11. let id: symbol = Symbol("id");
  12. // bigint类型
  13. let bigNumber: bigint = 9007199254740991n;
复制代码

数组和元组

数组是相同类型元素的集合,而元组则是固定长度、每个元素类型已知的数组:
  1. // 数组类型
  2. let numbers: number[] = [1, 2, 3, 4, 5];
  3. // 或者使用泛型语法
  4. let strings: Array<string> = ["hello", "world"];
  5. // 元组类型 - 固定长度和类型的数组
  6. let tuple: [string, number] = ["Alice", 30];
  7. // 可以添加命名元组提高可读性
  8. let user: [name: string, age: number] = ["Bob", 25];
复制代码

枚举

枚举允许我们定义一组命名常量:
  1. // 数字枚举
  2. enum Direction {
  3.   Up,    // 0
  4.   Down,  // 1
  5.   Left,  // 2
  6.   Right  // 3
  7. }
  8. // 字符串枚举
  9. enum Status {
  10.   Success = "SUCCESS",
  11.   Error = "ERROR",
  12.   Pending = "PENDING"
  13. }
  14. // 使用枚举
  15. let currentDirection: Direction = Direction.Up;
  16. let currentStatus: Status = Status.Success;
复制代码

any和unknown的区别

any类型会绕过类型检查,而unknown类型更安全,要求在使用前进行类型检查:
  1. // any类型 - 绕过所有类型检查
  2. let data: any = "hello";
  3. data = 42;
  4. data.toUpperCase(); // 运行时可能出错,但TypeScript不报错
  5. // unknown类型 - 更安全的替代方案
  6. let safeData: unknown = "hello";
  7. safeData = 42;
  8. // 使用unknown前必须进行类型检查
  9. if (typeof safeData === "string") {
  10.   console.log(safeData.toUpperCase()); // 安全
  11. }
  12. // 或者使用类型断言
  13. console.log((safeData as string).toUpperCase()); // 有风险,除非确定类型
复制代码

void和never类型

void表示函数没有返回值,而never表示永远不会返回的函数:
  1. // void类型 - 函数没有返回值
  2. function logMessage(message: string): void {
  3.   console.log(message);
  4.   // 没有return语句或返回undefined
  5. }
  6. // never类型 - 永远不会返回的函数
  7. function throwError(message: string): never {
  8.   throw new Error(message);
  9.   // 函数执行到这里会抛出错误,永远不会返回
  10. }
  11. function infiniteLoop(): never {
  12.   while (true) {
  13.     // 无限循环,永远不会返回
  14.   }
  15. }
复制代码

接口和类型别名

接口定义和使用

接口是TypeScript中定义对象结构的主要方式:
  1. // 基本接口
  2. interface User {
  3.   id: number;
  4.   name: string;
  5.   email: string;
  6. }
  7. // 使用接口
  8. function greetUser(user: User) {
  9.   return `Hello, ${user.name}!`;
  10. }
  11. const user: User = {
  12.   id: 1,
  13.   name: "Alice",
  14.   email: "alice@example.com"
  15. };
  16. console.log(greetUser(user));
复制代码

可选属性和只读属性

接口可以定义可选属性和只读属性:
  1. interface Product {
  2.   readonly id: number;      // 只读属性,创建后不能修改
  3.   name: string;
  4.   price?: number;           // 可选属性,使用?标记
  5.   description?: string;     // 可选属性
  6. }
  7. const product: Product = {
  8.   id: 1001,
  9.   name: "Laptop"
  10. };
  11. // 以下代码会报错,因为id是只读的
  12. // product.id = 1002; // Error: Cannot assign to 'id' because it is read-only
  13. // 可以添加可选属性
  14. product.price = 999.99;
复制代码

索引签名

索引签名允许定义可以使用不同键访问的对象类型:
  1. // 字符串索引签名
  2. interface StringDictionary {
  3.   [key: string]: string;
  4. }
  5. const capitals: StringDictionary = {
  6.   USA: "Washington D.C.",
  7.   UK: "London",
  8.   Japan: "Tokyo"
  9. };
  10. // 数字索引签名
  11. interface NumberArray {
  12.   [index: number]: string;
  13. }
  14. const names: NumberArray = ["Alice", "Bob", "Charlie"];
  15. console.log(names[0]); // "Alice"
  16. // 混合索引签名
  17. interface ExtendedDictionary {
  18.   [key: string]: string | number;
  19.   length: number; // 可以添加特定属性
  20. }
复制代码

类型别名

类型别名为类型提供新名称,可以用于任何类型:
  1. // 基本类型别名
  2. type Age = number;
  3. // 对象类型别名
  4. type Person = {
  5.   name: string;
  6.   age: Age;
  7.   email?: string;
  8. };
  9. // 联合类型别名
  10. type ID = string | number;
  11. // 函数类型别名
  12. type SearchFunc = (source: string, subString: string) => boolean;
  13. // 使用类型别名
  14. const person: Person = {
  15.   name: "John",
  16.   age: 30
  17. };
  18. const getUserId = (): ID => "123-45-6789";
复制代码

接口vs类型别名

接口和类型别名有相似之处,但也有一些关键区别:
  1. // 接口可以扩展
  2. interface Animal {
  3.   name: string;
  4. }
  5. interface Dog extends Animal {
  6.   breed: string;
  7. }
  8. // 类型别名可以使用交叉类型模拟扩展
  9. type AnimalType = {
  10.   name: string;
  11. };
  12. type DogType = AnimalType & {
  13.   breed: string;
  14. };
  15. // 接口可以合并声明
  16. interface Box {
  17.   height: number;
  18. }
  19. interface Box {
  20.   width: number;
  21. }
  22. // 现在Box包含height和width
  23. const box: Box = { height: 5, width: 6 };
  24. // 类型别名不能合并,会报错
  25. /*
  26. type BoxType = {
  27.   height: number;
  28. }
  29. type BoxType = {  // Error: Duplicate identifier 'BoxType'
  30.   width: number;
  31. }
  32. */
复制代码

函数类型安全

函数参数和返回值类型

为函数参数和返回值添加类型可以确保函数被正确使用:
  1. // 基本函数类型
  2. function add(a: number, b: number): number {
  3.   return a + b;
  4. }
  5. // 函数表达式
  6. const multiply: (a: number, b: number) => number = function(a, b) {
  7.   return a * b;
  8. };
  9. // 箭头函数
  10. const divide = (a: number, b: number): number => a / b;
  11. // 使用函数类型别名
  12. type MathOperation = (x: number, y: number) => number;
  13. const subtract: MathOperation = (x, y) => x - y;
复制代码

可选参数和默认参数

TypeScript支持可选参数和默认参数:
  1. // 可选参数
  2. function buildName(firstName: string, lastName?: string): string {
  3.   if (lastName) {
  4.     return `${firstName} ${lastName}`;
  5.   }
  6.   return firstName;
  7. }
  8. console.log(buildName("John"));         // "John"
  9. console.log(buildName("John", "Doe")); // "John Doe"
  10. // 默认参数
  11. function greet(name: string = "Guest"): string {
  12.   return `Hello, ${name}!`;
  13. }
  14. console.log(greet());           // "Hello, Guest!"
  15. console.log(greet("Alice"));    // "Hello, Alice!"
复制代码

剩余参数

剩余参数允许函数接受任意数量的参数:
  1. // 剩余参数必须是数组类型
  2. function sum(...numbers: number[]): number {
  3.   return numbers.reduce((total, current) => total + current, 0);
  4. }
  5. console.log(sum(1, 2, 3));         // 6
  6. console.log(sum(10, 20, 30, 40)); // 100
  7. // 带有必需参数和剩余参数
  8. function join(separator: string, ...strings: string[]): string {
  9.   return strings.join(separator);
  10. }
  11. console.log(join(", ", "apple", "banana", "cherry")); // "apple, banana, cherry"
复制代码

函数重载

函数重载允许一个函数有多个调用签名:
  1. // 函数重载声明
  2. function makeDate(timestamp: number): Date;
  3. function makeDate(m: number, d: number, y: number): Date;
  4. function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
  5.   if (d !== undefined && y !== undefined) {
  6.     return new Date(y, mOrTimestamp, d);
  7.   } else {
  8.     return new Date(mOrTimestamp);
  9.   }
  10. }
  11. const d1 = makeDate(12345678);      // 使用第一个重载
  12. const d2 = makeDate(5, 6, 2023);   // 使用第二个重载
  13. // const d3 = makeDate(5, 6);       // 错误,没有匹配的重载
复制代码

泛型函数

泛型函数可以处理多种类型,同时保持类型安全:
  1. // 基本泛型函数
  2. function identity<T>(arg: T): T {
  3.   return arg;
  4. }
  5. // 使用泛型函数
  6. let output = identity<string>("hello");
  7. let output2 = identity(42); // 类型推断为number
  8. // 多个类型参数
  9. function pair<T, U>(first: T, second: U): [T, U] {
  10.   return [first, second];
  11. }
  12. const p = pair<string, number>("hello", 42);
  13. // 泛型约束
  14. function loggingIdentity<T extends { length: number }>(arg: T): T {
  15.   console.log(arg.length); // 现在我们可以访问length属性
  16.   return arg;
  17. }
  18. loggingIdentity("hello");       // 字符串有length属性
  19. loggingIdentity([1, 2, 3]);      // 数组有length属性
  20. // loggingIdentity(42);         // 错误,数字没有length属性
复制代码

类和泛型

类的基本类型注解

TypeScript为类添加了类型注解,使类更加类型安全:
  1. class Person {
  2.   // 属性类型注解
  3.   name: string;
  4.   age: number;
  5.   
  6.   // 构造函数参数类型注解
  7.   constructor(name: string, age: number) {
  8.     this.name = name;
  9.     this.age = age;
  10.   }
  11.   
  12.   // 方法返回类型注解
  13.   greet(): string {
  14.     return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
  15.   }
  16. }
  17. const person = new Person("Alice", 30);
  18. console.log(person.greet());
复制代码

访问修饰符

TypeScript支持访问修饰符来控制类成员的可访问性:
  1. class Employee {
  2.   // public: 任何地方都可以访问
  3.   public name: string;
  4.   
  5.   // private: 只能在类内部访问
  6.   private id: number;
  7.   
  8.   // protected: 只能在类及其子类中访问
  9.   protected department: string;
  10.   
  11.   // readonly: 只读属性
  12.   readonly company: string = "ABC Corp";
  13.   
  14.   constructor(name: string, id: number, department: string) {
  15.     this.name = name;
  16.     this.id = id;
  17.     this.department = department;
  18.   }
  19.   
  20.   public getInfo(): string {
  21.     return `${this.name} (ID: ${this.id}) works in ${this.department}`;
  22.   }
  23.   
  24.   protected getDepartment(): string {
  25.     return this.department;
  26.   }
  27. }
  28. class Manager extends Employee {
  29.   private teamSize: number;
  30.   
  31.   constructor(name: string, id: number, department: string, teamSize: number) {
  32.     super(name, id, department);
  33.     this.teamSize = teamSize;
  34.   }
  35.   
  36.   public getManagerInfo(): string {
  37.     // 可以访问父类的protected成员
  38.     const dept = this.getDepartment();
  39.     return `${this.name} manages ${this.teamSize} people in ${dept}`;
  40.   }
  41. }
  42. const emp = new Employee("John", 123, "IT");
  43. console.log(emp.name);     // 可以访问public成员
  44. // console.log(emp.id);    // 错误,不能访问private成员
  45. // console.log(emp.department); // 错误,不能访问protected成员
  46. const manager = new Manager("Alice", 456, "Finance", 10);
  47. console.log(manager.getManagerInfo());
复制代码

抽象类和接口实现

抽象类和接口可以帮助定义类的结构:
  1. // 抽象类
  2. abstract class Animal {
  3.   abstract makeSound(): void; // 必须在派生类中实现
  4.   
  5.   move(): void {
  6.     console.log("Moving along!");
  7.   }
  8. }
  9. // 接口
  10. interface Flyable {
  11.   fly(): void;
  12.   altitude: number;
  13. }
  14. // 实现抽象类和接口
  15. class Bird extends Animal implements Flyable {
  16.   altitude: number;
  17.   
  18.   constructor(altitude: number) {
  19.     super();
  20.     this.altitude = altitude;
  21.   }
  22.   
  23.   makeSound(): void {
  24.     console.log("Tweet tweet!");
  25.   }
  26.   
  27.   fly(): void {
  28.     console.log(`Flying at ${this.altitude} meters`);
  29.   }
  30. }
  31. const bird = new Bird(100);
  32. bird.makeSound(); // "Tweet tweet!"
  33. bird.move();      // "Moving along!"
  34. bird.fly();       // "Flying at 100 meters"
复制代码

泛型类

类也可以使用泛型来增加灵活性:
  1. class Box<T> {
  2.   private content: T;
  3.   
  4.   constructor(initialContent: T) {
  5.     this.content = initialContent;
  6.   }
  7.   
  8.   getContent(): T {
  9.     return this.content;
  10.   }
  11.   
  12.   setContent(newContent: T): void {
  13.     this.content = newContent;
  14.   }
  15. }
  16. // 使用泛型类
  17. const stringBox = new Box<string>("Hello");
  18. console.log(stringBox.getContent()); // "Hello"
  19. stringBox.setContent("World");
  20. const numberBox = new Box<number>(42);
  21. console.log(numberBox.getContent()); // 42
  22. numberBox.setContent(100);
  23. // 类型推断
  24. const booleanBox = new Box(true);
  25. console.log(booleanBox.getContent()); // true
复制代码

泛型约束

泛型约束可以限制泛型参数的类型:
  1. // 泛型约束
  2. interface Lengthy {
  3.   length: number;
  4. }
  5. class Collection<T extends Lengthy> {
  6.   private items: T[] = [];
  7.   
  8.   addItem(item: T): void {
  9.     this.items.push(item);
  10.   }
  11.   
  12.   getFirstLongerThan(length: number): T | undefined {
  13.     return this.items.find(item => item.length > length);
  14.   }
  15.   
  16.   getAverageLength(): number {
  17.     const totalLength = this.items.reduce((sum, item) => sum + item.length, 0);
  18.     return this.items.length ? totalLength / this.items.length : 0;
  19.   }
  20. }
  21. // 使用泛型约束类
  22. const stringCollection = new Collection<string>();
  23. stringCollection.addItem("Hello");
  24. stringCollection.addItem("World");
  25. stringCollection.addItem("TypeScript");
  26. console.log(stringCollection.getFirstLongerThan(5)); // "TypeScript"
  27. console.log(stringCollection.getAverageLength());   // 5.666...
  28. const arrayCollection = new Collection<number[]>();
  29. arrayCollection.addItem([1, 2, 3]);
  30. arrayCollection.addItem([4, 5]);
  31. console.log(arrayCollection.getFirstLongerThan(2)); // [1, 2, 3]
  32. console.log(arrayCollection.getAverageLength());   // 2.5
复制代码

高级类型技巧

联合类型和交叉类型

联合类型允许一个值可以是多种类型之一,交叉类型则组合多个类型:
  1. // 联合类型
  2. function formatValue(value: string | number): string {
  3.   if (typeof value === "string") {
  4.     return value.toUpperCase();
  5.   } else {
  6.     return value.toFixed(2);
  7.   }
  8. }
  9. console.log(formatValue("hello")); // "HELLO"
  10. console.log(formatValue(42.5678)); // "42.57"
  11. // 交叉类型
  12. interface BusinessPartner {
  13.   name: string;
  14.   credit: number;
  15. }
  16. interface Identity {
  17.   id: number;
  18.   name: string;
  19. }
  20. type Employee = BusinessPartner & Identity;
  21. const employee: Employee = {
  22.   id: 123,
  23.   name: "John Doe",
  24.   credit: 7500
  25. };
  26. // 类型收窄
  27. function printValue(value: string | number) {
  28.   if (typeof value === "string") {
  29.     // 在这个块中,TypeScript知道value是string类型
  30.     console.log(value.toUpperCase());
  31.   } else {
  32.     // 在这个块中,TypeScript知道value是number类型
  33.     console.log(value.toFixed(2));
  34.   }
  35. }
复制代码

类型推断

TypeScript能够根据上下文自动推断类型:
  1. // 变量类型推断
  2. let message = "Hello"; // 推断为string类型
  3. message = 42; // 错误:不能将number类型分配给string类型
  4. // 函数返回类型推断
  5. function add(a: number, b: number) {
  6.   return a + b; // 推断返回类型为number
  7. }
  8. // 最佳通用类型推断
  9. let arr = [0, 1, null]; // 推断为(number | null)[]
复制代码

映射类型

映射类型基于现有类型创建新类型:
  1. interface Person {
  2.   name: string;
  3.   age: number;
  4.   address: string;
  5. }
  6. // 创建所有属性可选的映射类型
  7. type PartialPerson = {
  8.   [K in keyof Person]?: Person[K];
  9. };
  10. // 创建所有属性只读的映射类型
  11. type ReadonlyPerson = {
  12.   readonly [K in keyof Person]: Person[K];
  13. };
  14. // 使用映射类型
  15. const partialPerson: PartialPerson = {
  16.   name: "John"
  17.   // age和address是可选的
  18. };
  19. const readonlyPerson: ReadonlyPerson = {
  20.   name: "Alice",
  21.   age: 30,
  22.   address: "123 Main St"
  23. };
  24. // readonlyPerson.name = "Bob"; // 错误,不能修改只读属性
复制代码

条件类型

条件类型根据条件选择一种类型:
  1. // 基本条件类型
  2. type NonNullable<T> = T extends null | undefined ? never : T;
  3. // 使用条件类型
  4. type StringOrNumber = string | number;
  5. type OnlyString = NonNullable<StringOrNumber>; // string | number
  6. // 条件类型与映射类型结合
  7. type ExtractProperties<T, U> = {
  8.   [K in keyof T]: T[K] extends U ? K : never;
  9. }[keyof T];
  10. interface Product {
  11.   id: number;
  12.   name: string;
  13.   price: number;
  14.   inStock: boolean;
  15. }
  16. // 提取所有number类型的属性名
  17. type NumberProperties = ExtractProperties<Product, number>; // "id" | "price"
复制代码

工具类型

TypeScript提供了一些内置的工具类型,方便常见的类型转换:
  1. // Partial - 将所有属性变为可选
  2. interface Todo {
  3.   title: string;
  4.   description: string;
  5.   completed: boolean;
  6. }
  7. type PartialTodo = Partial<Todo>;
  8. // 相当于:
  9. // type PartialTodo = {
  10. //   title?: string;
  11. //   description?: string;
  12. //   completed?: boolean;
  13. // }
  14. // Required - 将所有属性变为必需
  15. type RequiredTodo = Required<PartialTodo>;
  16. // 相当于原来的Todo
  17. // Pick - 选择一组属性
  18. type TodoPreview = Pick<Todo, "title" | "completed">;
  19. // 相当于:
  20. // type TodoPreview = {
  21. //   title: string;
  22. //   completed: boolean;
  23. // }
  24. // Omit - 排除一组属性
  25. type TodoWithoutDescription = Omit<Todo, "description">;
  26. // 相当于TodoPreview
  27. // Record - 创建对象类型
  28. type PageInfo = {
  29.   title: string;
  30. };
  31. type Page = "home" | "about" | "contact";
  32. type Pages = Record<Page, PageInfo>;
  33. // 相当于:
  34. // type Pages = {
  35. //   home: PageInfo;
  36. //   about: PageInfo;
  37. //   contact: PageInfo;
  38. // }
  39. // Exclude - 排除类型
  40. type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
  41. // Extract - 提取类型
  42. type T1 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
  43. // ReturnType - 获取函数返回类型
  44. function createUserId(name: string, id: number): string {
  45.   return `${name}-${id}`;
  46. }
  47. type UserId = ReturnType<typeof createUserId>; // string
复制代码

类型守卫和类型断言

typeof和instanceof类型守卫

类型守卫帮助TypeScript在特定上下文中收窄类型:
  1. // typeof类型守卫
  2. function processValue(value: string | number) {
  3.   if (typeof value === "string") {
  4.     // 在这个块中,value被收窄为string类型
  5.     console.log(value.toUpperCase());
  6.   } else {
  7.     // 在这个块中,value被收窄为number类型
  8.     console.log(value.toFixed(2));
  9.   }
  10. }
  11. // instanceof类型守卫
  12. class Cat {
  13.   meow() {}
  14. }
  15. class Dog {
  16.   bark() {}
  17. }
  18. function processAnimal(animal: Cat | Dog) {
  19.   if (animal instanceof Cat) {
  20.     // 在这个块中,animal被收窄为Cat类型
  21.     animal.meow();
  22.   } else {
  23.     // 在这个块中,animal被收窄为Dog类型
  24.     animal.bark();
  25.   }
  26. }
复制代码

自定义类型守卫

可以创建自定义类型守卫函数:
  1. // 自定义类型守卫
  2. interface Fish {
  3.   swim(): void;
  4. }
  5. interface Bird {
  6.   fly(): void;
  7. }
  8. function isFish(pet: Fish | Bird): pet is Fish {
  9.   return (pet as Fish).swim !== undefined;
  10. }
  11. function processPet(pet: Fish | Bird) {
  12.   if (isFish(pet)) {
  13.     // 在这个块中,pet被收窄为Fish类型
  14.     pet.swim();
  15.   } else {
  16.     // 在这个块中,pet被收窄为Bird类型
  17.     pet.fly();
  18.   }
  19. }
复制代码

类型断言的语法和用法

类型断言告诉TypeScript将一个值当作特定类型处理:
  1. // 类型断言的两种语法
  2. let someValue: unknown = "hello";
  3. let strLength: number = (someValue as string).length;
  4. // 或者
  5. let strLength2: number = (<string>someValue).length;
  6. // 类型断言在DOM操作中的常见用法
  7. const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
  8. // 或者
  9. const myCanvas2 = <HTMLCanvasElement>document.getElementById("main_canvas");
  10. // 类型断言与联合类型
  11. interface Employee {
  12.   name: string;
  13.   code: number;
  14. }
  15. interface Person {
  16.   name: string;
  17.   age: number;
  18. }
  19. let employee: Employee | Person = getEmployee(); // 假设这个函数返回Employee或Person
  20. // 使用类型断言访问特定属性
  21. if ((employee as Employee).code !== undefined) {
  22.   // 在这个块中,我们知道employee是Employee类型
  23.   console.log((employee as Employee).code);
  24. }
复制代码

非空断言

非空断言操作符(!)告诉TypeScript一个值不是null或undefined:
  1. // 非空断言
  2. function getLength(value: string | null | undefined): number {
  3.   // 使用非空断言
  4.   return value!.length;
  5. }
  6. // 但这样更安全
  7. function getLengthSafe(value: string | null | undefined): number {
  8.   if (value) {
  9.     return value.length;
  10.   }
  11.   return 0;
  12. }
  13. // 在DOM操作中的使用
  14. const button = document.getElementById("submit-button");
  15. button!.addEventListener("click", () => {
  16.   // 处理点击事件
  17. });
  18. // 或者更安全的方式
  19. const button2 = document.getElementById("submit-button");
  20. if (button2) {
  21.   button2.addEventListener("click", () => {
  22.     // 处理点击事件
  23.   });
  24. }
复制代码

明确赋值断言

明确赋值断言(!)告诉TypeScript一个属性会被初始化,即使编译器无法检测到:
  1. class Point {
  2.   x!: number;
  3.   y!: number;
  4.   
  5.   constructor() {
  6.     this.initialize();
  7.   }
  8.   
  9.   initialize() {
  10.     this.x = 0;
  11.     this.y = 0;
  12.   }
  13. }
  14. // 没有明确赋值断言,TypeScript会报错,因为x和y在构造函数中没有初始化
复制代码

实用模式和最佳实践

严格的类型检查配置

在tsconfig.json中启用严格的类型检查选项:
  1. {
  2.   "compilerOptions": {
  3.     "strict": true,
  4.     "noImplicitAny": true,
  5.     "strictNullChecks": true,
  6.     "strictFunctionTypes": true,
  7.     "strictBindCallApply": true,
  8.     "strictPropertyInitialization": true,
  9.     "noImplicitThis": true,
  10.     "alwaysStrict": true,
  11.     "noUnusedLocals": true,
  12.     "noUnusedParameters": true,
  13.     "noImplicitReturns": true,
  14.     "noFallthroughCasesInSwitch": true
  15.   }
  16. }
复制代码

不可变数据模式

使用TypeScript的readonly修饰符和Readonly工具类型实现不可变数据:
  1. // 使用readonly修饰符
  2. interface ImmutableUser {
  3.   readonly id: number;
  4.   readonly name: string;
  5.   readonly email: string;
  6. }
  7. const user: ImmutableUser = {
  8.   id: 1,
  9.   name: "Alice",
  10.   email: "alice@example.com"
  11. };
  12. // user.name = "Bob"; // 错误,不能修改只读属性
  13. // 使用Readonly工具类型
  14. interface Config {
  15.   apiUrl: string;
  16.   timeout: number;
  17.   retries: number;
  18. }
  19. function createConfig(): Readonly<Config> {
  20.   return {
  21.     apiUrl: "https://api.example.com",
  22.     timeout: 5000,
  23.     retries: 3
  24.   };
  25. }
  26. const config = createConfig();
  27. // config.timeout = 10000; // 错误,不能修改只读属性
  28. // 使用ReadonlyArray
  29. const numbers: ReadonlyArray<number> = [1, 2, 3];
  30. // numbers.push(4); // 错误,push方法在ReadonlyArray上不存在
  31. // numbers[0] = 5; // 错误,不能修改只读数组的元素
复制代码

函数式编程与类型安全

结合函数式编程模式和TypeScript类型系统:
  1. // 函数组合
  2. type Func<T, R> = (x: T) => R;
  3. function compose<T, U, V>(f: Func<U, V>, g: Func<T, U>): Func<T, V> {
  4.   return (x: T) => f(g(x));
  5. }
  6. // 使用函数组合
  7. const toUpperCase = (str: string): string => str.toUpperCase();
  8. const exclaim = (str: string): string => `${str}!`;
  9. const shout = compose(exclaim, toUpperCase);
  10. console.log(shout("hello")); // "HELLO!"
  11. // 函数式数据结构 - 不可变链表
  12. type List<T> =
  13.   | { kind: "nil" }
  14.   | { kind: "cons"; head: T; tail: List<T> };
  15. function length<T>(list: List<T>): number {
  16.   switch (list.kind) {
  17.     case "nil": return 0;
  18.     case "cons": return 1 + length(list.tail);
  19.   }
  20. }
  21. function map<T, U>(f: (x: T) => U, list: List<T>): List<U> {
  22.   switch (list.kind) {
  23.     case "nil": return { kind: "nil" };
  24.     case "cons": return {
  25.       kind: "cons",
  26.       head: f(list.head),
  27.       tail: map(f, list.tail)
  28.     };
  29.   }
  30. }
  31. // 使用不可变链表
  32. const list: List<number> = {
  33.   kind: "cons",
  34.   head: 1,
  35.   tail: {
  36.     kind: "cons",
  37.     head: 2,
  38.     tail: { kind: "nil" }
  39.   }
  40. };
  41. console.log(length(list)); // 2
  42. const doubled = map(x => x * 2, list);
  43. console.log(length(doubled)); // 2
复制代码

错误处理策略

使用类型安全的错误处理模式:
  1. // Result模式 - 显式处理成功和失败情况
  2. type Result<T, E> =
  3.   | { kind: "success"; value: T }
  4.   | { kind: "error"; error: E };
  5. function parseJson<T>(json: string): Result<T, Error> {
  6.   try {
  7.     const parsed = JSON.parse(json);
  8.     return { kind: "success", value: parsed };
  9.   } catch (error) {
  10.     return { kind: "error", error: error as Error };
  11.   }
  12. }
  13. // 使用Result模式
  14. const result = parseJson<{ name: string }>('{"name": "Alice"}');
  15. if (result.kind === "success") {
  16.   console.log(result.value.name); // 类型安全访问
  17. } else {
  18.   console.error(result.error.message);
  19. }
  20. // Either类型 - 类似于Result但更通用
  21. type Either<L, R> = Left<L> | Right<R>;
  22. class Left<L> {
  23.   readonly kind = "left";
  24.   constructor(readonly value: L) {}
  25. }
  26. class Right<R> {
  27.   readonly kind = "right";
  28.   constructor(readonly value: R) {}
  29. }
  30. function isLeft<L, R>(e: Either<L, R>): e is Left<L> {
  31.   return e.kind === "left";
  32. }
  33. function isRight<L, R>(e: Either<L, R>): e is Right<R> {
  34.   return e.kind === "right";
  35. }
  36. // 使用Either类型
  37. function divide(a: number, b: number): Either<string, number> {
  38.   if (b === 0) {
  39.     return new Left("Cannot divide by zero");
  40.   }
  41.   return new Right(a / b);
  42. }
  43. const result2 = divide(10, 2);
  44. if (isRight(result2)) {
  45.   console.log(result2.value); // 5
  46. }
  47. const result3 = divide(10, 0);
  48. if (isLeft(result3)) {
  49.   console.error(result3.value); // "Cannot divide by zero"
  50. }
复制代码

测试与类型安全

结合TypeScript和测试框架确保代码质量:
  1. // 使用Jest和TypeScript进行类型安全测试
  2. import { sum } from './math';
  3. describe('sum function', () => {
  4.   it('should add two numbers correctly', () => {
  5.     // TypeScript会检查参数类型
  6.     const result = sum(2, 3);
  7.     expect(result).toBe(5);
  8.   });
  9.   
  10.   it('should handle negative numbers', () => {
  11.     const result = sum(-2, -3);
  12.     expect(result).toBe(-5);
  13.   });
  14.   
  15.   // 以下测试会在编译时报错,因为参数类型不匹配
  16.   // it('should not accept strings', () => {
  17.   //   const result = sum('2', '3');
  18.   //   expect(result).toBe(5);
  19.   // });
  20. });
  21. // 使用类型守卫进行运行时类型检查
  22. function isString(value: unknown): value is string {
  23.   return typeof value === "string";
  24. }
  25. function processInput(input: unknown) {
  26.   if (isString(input)) {
  27.     // 在这个块中,TypeScript知道input是string类型
  28.     console.log(input.toUpperCase());
  29.   } else {
  30.     console.error("Input must be a string");
  31.   }
  32. }
  33. // 使用zod进行运行时类型验证
  34. import { z } from "zod";
  35. const UserSchema = z.object({
  36.   id: z.number(),
  37.   name: z.string(),
  38.   email: z.string().email(),
  39. });
  40. type User = z.infer<typeof UserSchema>;
  41. function parseUser(data: unknown): User {
  42.   return UserSchema.parse(data);
  43. }
  44. // 使用示例
  45. try {
  46.   const user = parseUser({
  47.     id: 1,
  48.     name: "Alice",
  49.     email: "alice@example.com"
  50.   });
  51.   console.log(user.name);
  52. } catch (error) {
  53.   console.error("Invalid user data", error);
  54. }
复制代码

工具配置和工程化

tsconfig.json配置详解

一个完整的tsconfig.json配置示例:
  1. {
  2.   "compilerOptions": {
  3.     /* 基本选项 */
  4.     "target": "ES2020",                  // 指定ECMAScript目标版本
  5.     "module": "commonjs",                // 指定模块代码生成
  6.     "lib": ["ES2020", "DOM", "DOM.Iterable"], // 指定要包含在编译中的库文件
  7.    
  8.     /* 模块解析选项 */
  9.     "moduleResolution": "node",          // 决定如何处理模块
  10.     "baseUrl": "./",                     // 用于解析非绝对模块名称的基目录
  11.     "paths": {                           // 一系列条目用于重新映射模块导入位置
  12.       "@/*": ["src/*"]
  13.     },
  14.     "esModuleInterop": true,             // 通过为所有导入创建命名空间对象,实现CommonJS和ES模块之间的互操作性
  15.    
  16.     /* 严格类型检查选项 */
  17.     "strict": true,                      // 启用所有严格类型检查选项
  18.     "noImplicitAny": true,               // 在表达式和声明上有隐含的any类型时报错
  19.     "strictNullChecks": true,            // 严格的null检查
  20.     "strictFunctionTypes": true,         // 严格的函数类型检查
  21.     "strictBindCallApply": true,         // 严格的bind/call/apply检查
  22.     "strictPropertyInitialization": true, // 严格的类属性初始化检查
  23.     "noImplicitThis": true,              // 在this类型为any时报错
  24.     "alwaysStrict": true,                // 以严格模式解析并为每个源文件发出"use strict"
  25.    
  26.     /* 额外检查 */
  27.     "noUnusedLocals": true,              // 若有未使用的局部变量则报错
  28.     "noUnusedParameters": true,          // 若有未使用的参数则报错
  29.     "noImplicitReturns": true,           // 不是函数中的所有代码路径都返回值时报错
  30.     "noFallthroughCasesInSwitch": true,  // 在switch语句中报告fallthrough错误
  31.    
  32.     /* 高级选项 */
  33.     "skipLibCheck": true,                // 跳过对声明文件的类型检查
  34.     "forceConsistentCasingInFileNames": true, // 禁止对同一文件使用不一致的大小写引用
  35.    
  36.     /* 源映射选项 */
  37.     "sourceMap": true,                   // 生成相应的'.map'文件
  38.     "declaration": true,                 // 生成相应的'.d.ts'文件
  39.     "declarationMap": true,              // 为声明文件生成sourcemap
  40.    
  41.     /* 构建选项 */
  42.     "outDir": "./dist",                  // 重定向输出目录
  43.     "rootDir": "./src",                  // 指定输入文件的根目录
  44.    
  45.     /* 实验性选项 */
  46.     "experimentalDecorators": true,      // 启用对ES装饰器的实验性支持
  47.     "emitDecoratorMetadata": true        // 为装饰器提供元数据支持
  48.   },
  49.   "include": ["src/**/*"],              // 包含的文件
  50.   "exclude": ["node_modules", "**/*.spec.ts"] // 排除的文件
  51. }
复制代码

与构建工具的集成

TypeScript与各种构建工具的集成示例:
  1. // webpack.config.js 与 TypeScript集成
  2. const path = require('path');
  3. module.exports = {
  4.   entry: './src/index.ts',
  5.   module: {
  6.     rules: [
  7.       {
  8.         test: /\.tsx?$/,
  9.         use: 'ts-loader',
  10.         exclude: /node_modules/,
  11.       },
  12.     ],
  13.   },
  14.   resolve: {
  15.     extensions: ['.tsx', '.ts', '.js'],
  16.   },
  17.   output: {
  18.     filename: 'bundle.js',
  19.     path: path.resolve(__dirname, 'dist'),
  20.   },
  21. };
  22. // Vite 配置 (vite.config.ts)
  23. import { defineConfig } from 'vite';
  24. import react from '@vitejs/plugin-react';
  25. export default defineConfig({
  26.   plugins: [react()],
  27.   resolve: {
  28.     alias: {
  29.       '@': path.resolve(__dirname, './src'),
  30.     },
  31.   },
  32.   server: {
  33.     port: 3000,
  34.   },
  35.   build: {
  36.     outDir: 'dist',
  37.     sourcemap: true,
  38.   },
  39. });
  40. // Rollup 配置 (rollup.config.js)
  41. import typescript from '@rollup/plugin-typescript';
  42. import { nodeResolve } from '@rollup/plugin-node-resolve';
  43. import commonjs from '@rollup/plugin-commonjs';
  44. export default {
  45.   input: 'src/index.ts',
  46.   output: {
  47.     file: 'dist/bundle.js',
  48.     format: 'cjs',
  49.   },
  50.   plugins: [
  51.     typescript(),
  52.     nodeResolve(),
  53.     commonjs(),
  54.   ],
  55. };
复制代码

代码检查工具

使用ESLint和Prettier进行代码检查和格式化:
  1. // .eslintrc.js
  2. module.exports = {
  3.   parser: '@typescript-eslint/parser',
  4.   plugins: ['@typescript-eslint'],
  5.   extends: [
  6.     'eslint:recommended',
  7.     '@typescript-eslint/recommended',
  8.     '@typescript-eslint/recommended-requiring-type-checking',
  9.   ],
  10.   parserOptions: {
  11.     ecmaVersion: 2020,
  12.     sourceType: 'module',
  13.     project: './tsconfig.json',
  14.   },
  15.   rules: {
  16.     // 自定义规则
  17.     '@typescript-eslint/no-unused-vars': 'error',
  18.     '@typescript-eslint/explicit-function-return-type': 'warn',
  19.     '@typescript-eslint/no-explicit-any': 'warn',
  20.   },
  21. };
  22. // .prettierrc
  23. {
  24.   "semi": true,
  25.   "trailingComma": "all",
  26.   "singleQuote": true,
  27.   "printWidth": 120,
  28.   "tabWidth": 2
  29. }
  30. // package.json中的脚本
  31. {
  32.   "scripts": {
  33.     "lint": "eslint src --ext .ts,.tsx",
  34.     "lint:fix": "eslint src --ext .ts,.tsx --fix",
  35.     "format": "prettier --write "src/**/*.{ts,tsx}"",
  36.     "type-check": "tsc --noEmit"
  37.   }
  38. }
复制代码

自动化类型测试

使用TypeScript的API进行自动化类型测试:
  1. // type-test.ts
  2. import ts from 'typescript';
  3. import path from 'path';
  4. function compileTypeScript(filePath: string) {
  5.   const configPath = ts.findConfigFile(
  6.     process.cwd(),
  7.     ts.sys.fileExists,
  8.     'tsconfig.json'
  9.   );
  10.   
  11.   if (!configPath) {
  12.     throw new Error('Could not find tsconfig.json');
  13.   }
  14.   
  15.   const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
  16.   const config = ts.parseJsonConfigFileContent(
  17.     configFile.config,
  18.     ts.sys,
  19.     path.dirname(configPath)
  20.   );
  21.   
  22.   const program = ts.createProgram([filePath], config.options);
  23.   const emitResult = program.emit();
  24.   
  25.   const allDiagnostics = ts
  26.     .getPreEmitDiagnostics(program)
  27.     .concat(emitResult.diagnostics);
  28.    
  29.   return allDiagnostics;
  30. }
  31. // 运行类型测试
  32. const diagnostics = compileTypeScript('src/example.ts');
  33. diagnostics.forEach(diagnostic => {
  34.   if (diagnostic.file) {
  35.     const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(
  36.       diagnostic.start!
  37.     );
  38.     const message = ts.flattenDiagnosticMessageText(
  39.       diagnostic.messageText,
  40.       '\n'
  41.     );
  42.     console.log(
  43.       `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`
  44.     );
  45.   } else {
  46.     console.log(
  47.       ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')
  48.     );
  49.   }
  50. });
  51. // 如果有诊断信息,则退出码为1
  52. process.exit(diagnostics.length > 0 ? 1 : 0);
复制代码

持续集成中的类型检查

在GitHub Actions中设置TypeScript类型检查:
  1. # .github/workflows/type-check.yml
  2. name: Type Check
  3. on:
  4.   push:
  5.     branches: [ main ]
  6.   pull_request:
  7.     branches: [ main ]
  8. jobs:
  9.   type-check:
  10.     runs-on: ubuntu-latest
  11.    
  12.     steps:
  13.     - uses: actions/checkout@v2
  14.    
  15.     - name: Setup Node.js
  16.       uses: actions/setup-node@v2
  17.       with:
  18.         node-version: '16'
  19.         cache: 'npm'
  20.    
  21.     - name: Install dependencies
  22.       run: npm ci
  23.    
  24.     - name: Run TypeScript type checking
  25.       run: npm run type-check
  26.    
  27.     - name: Run ESLint
  28.       run: npm run lint
复制代码

总结

TypeScript的类型系统为JavaScript开发者提供了强大的工具,用于编写更安全、更可维护的代码。从基础类型到高级技巧,TypeScript的类型安全特性可以帮助我们在开发过程中捕获潜在的错误,减少运行时bug。

本文涵盖了TypeScript类型系统的各个方面,包括:

1. 基本类型系统和类型注解
2. 接口和类型别名的使用
3. 函数类型安全和泛型
4. 类和泛型的高级应用
5. 高级类型技巧,如联合类型、交叉类型和条件类型
6. 类型守卫和类型断言的使用
7. 实用的类型安全模式和最佳实践
8. 工具配置和工程化实践

通过掌握这些概念和技巧,你可以充分利用TypeScript的类型系统,构建更健壮、更可靠的应用程序。随着TypeScript的不断发展,类型安全编程将成为前端和后端开发的标准实践,帮助我们构建更高质量的软件。

要进一步提升你的TypeScript技能,建议:

1. 深入学习TypeScript官方文档和高级类型系统
2. 参与开源项目,观察大型项目中TypeScript的使用方式
3. 尝试将现有的JavaScript项目迁移到TypeScript
4. 探索TypeScript与其他工具和框架的集成
5. 关注TypeScript社区的最新发展和最佳实践

通过持续学习和实践,你将能够充分利用TypeScript的类型系统,编写出更加安全、可维护的代码,打造出真正无bug的应用程序。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

手机版|联系我们|小黑屋|TG频道|RSS |网站地图

Powered by Pixtech

© 2025-2026 Pixtech Team.

>