likes
comments
collection
share

TypeScript 初探:前端开发新手的学习之路

作者站长头像
站长
· 阅读数 4

认识TypeScript

什么是TypeScript?

TypeScript是JavaScript的增强版本,引入了静态类型检查和更多面向对象的特性,以提高代码质量和可维护性。

为什么要学TypeScript?

  • 强大的类型系统: 提前捕捉错误,改善代码稳定性。

  • 面向对象编程: 类、接口等特性,提高代码结构清晰度。

  • 协作效率: 类型定义减少团队协作中的误解,提高开发效率。

  • 优秀的工具支持: 集成开发工具,如Visual Studio Code,提升开发体验。

TypeScript和JavaScript的关系

TypeScript是JavaScript的超集,兼容所有JavaScript代码。可逐步迁移项目,享受静态类型检查等优势,编译器将其转换为可在任何支持JavaScript的环境中运行的代码。学习TypeScript提升现代前端开发应对挑战的能力。

搭建开发环境

安装Node.js和npm

  • Node.js安装: 访问 nodejs.org,下载并安装最新版本的Node.js。Node.js自带npm(Node包管理器)。

使用npm安装TypeScript

  • 全局安装TypeScript: 打开终端或命令提示符,运行以下命令安装全局TypeScript:
npm install -g typescript

配置TypeScript编译器

  • 创建tsconfig.json文件: 在项目根目录下创建一个tsconfig.json文件,用于配置TypeScript编译器的设置。可以通过以下命令生成:
tsc --init
  • 编辑tsconfig.json: 根据项目需求,调整tsconfig.json中的配置,例如指定编译输出目录、调整目标JavaScript版本等。
{
  "compilerOptions": {
    "target": "es5",
    "outDir": "./dist",
    "strict": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}
  • "target":生成的JavaScript版本。
  • "module":生成的JavaScript模块系统。
  • "outDir":编译后JavaScript文件存放的目录。
  • "strict":启用所有严格类型检查选项。

通过以上步骤,你成功搭建了TypeScript开发环境。接下来,编写你的TypeScript代码,例如在src目录下创建一个main.ts文件:

// src/main.ts
function greet(name: string): string {
  return `Hello, ${name}!`;
}

console.log(greet("TypeScript"));

然后,在终端中运行以下命令编译和执行:

tsc          // 编译TypeScript代码
node dist/main.js   // 运行编译后的JavaScript代码

这将输出 Hello, TypeScript!,说明你的TypeScript代码已经成功运行。

基础语法入门

变量和数据类型:

声明变量与赋值

在 TypeScript 中,变量的声明可以使用 letconst 关键字。let 用于声明可变的变量,而 const 用于声明不可变的常量。

let age: number = 25;
const name: string = "John";

基本数据类型

number
let count: number = 42;
string
let message: string = "Hello, TypeScript!";
boolean
let isActive: boolean = true;
any 类型

在某些情况下,我们可能不知道变量的类型,可以使用 any 类型:

let variable: any = 10;
variable = "Hello, TypeScript!";

类型别名

使用 type 关键字定义类型别名

在 TypeScript 中,你可以使用 type 关键字创建类型别名。类型别名是一个已存在类型的新名称,可以用于简化复杂类型的定义。

示例:

// 使用类型别名定义对象类型
type Point = {
    x: number;
    y: number;
};

// 使用类型别名定义函数类型
type MathFunction = (x: number, y: number) => number;

在上述示例中,Point 是一个对象类型的类型别名,表示包含 xy 属性的点。MathFunction 是一个函数类型的类型别名,表示接受两个参数并返回一个数字的函数。

简化复杂类型的定义

类型别名常用于简化复杂类型的定义,提高代码的可读性和维护性。通过为复杂类型起一个清晰的名称,可以更容易理解和使用这些类型。

示例:

// 复杂类型的定义
type User = {
    id: number;
    name: string;
    age: number;
    isAdmin: boolean;
};

type Point3D = {
    x: number;
    y: number;
    z: number;
};

// 使用类型别名简化类型定义
type ComplexType = {
    user: User;
    position: Point3D;
};

在这个示例中,ComplexType 是一个包含 UserPoint3D 的复杂类型的类型别名,使其更易于使用和理解。

类型推断

根据赋值语句自动推断变量类型

TypeScript 具有类型推断的能力,即它可以根据变量的赋值语句自动推断出变量的类型。

示例:

// 类型推断
let age = 25; // TypeScript 推断 age 的类型为 number
let message = "Hello, TypeScript!"; // TypeScript 推断 message 的类型为 string

在这个示例中,变量 age 被赋值为 25,因此 TypeScript 推断它的类型为 number。变量 message 被赋值为字符串,所以 TypeScript 推断它的类型为 string

减少不必要的类型注解

由于 TypeScript 具有类型推断,有时候不必手动注明变量的类型,让 TypeScript 根据上下文自动推断类型可以减少冗长的类型注解。

示例:

// 不必要的类型注解
let numberArray: number[] = [1, 2, 3, 4, 5];

// 简化写法,TypeScript 根据赋值语句推断数组类型
let simplifiedNumberArray = [1, 2, 3, 4, 5];

在这个示例中,变量 numberArray 被手动注明为 number 类型的数组,而变量 simplifiedNumberArray 则根据赋值语句自动推断为相同的类型,减少了不必要的类型注解。

Null 和 Undefined

nullundefined 类型

在 TypeScript 中,nullundefined 是两个特殊的类型,分别表示值为 null 和值为 undefined。它们通常用于表示缺失或不存在的值。

示例:

// null 和 undefined 类型
let nullValue: null = null;
let undefinedValue: undefined = undefined;

// 可赋值给任意类型
let numberValue: number = null;
let stringValue: string = undefined;

在这个示例中,nullValue 的类型被明确指定为 null,同理,undefinedValue 的类型被指定为 undefined

严格空值检查的设置

TypeScript 提供了严格空值检查的选项,可以通过在 tsconfig.json 文件中设置 "strictNullChecks": true 来启用。启用严格空值检查后,变量不能被赋值为 nullundefined,除非显式声明为这两个类型。

示例:

// 启用严格空值检查
// tsconfig.json
// {
//   "compilerOptions": {
//     "strictNullChecks": true
//   }
// }

let nonNullableString: string = "Hello";
// 错误,不能将 null 赋值给非允许的类型
let nullableString: string = null; // Error

通过启用严格空值检查,可以避免一些常见的空值相关错误,提高代码的健壮性和可维护性。

函数和参数:

函数的声明

在 TypeScript 中,可以使用 function 关键字声明函数。函数声明通常包括函数名、参数列表和返回类型的注解。

示例:

// 函数声明
function greet(name: string): string {
    return `Hello, ${name}!`;
}

在这个示例中,greet 是一个函数,接受一个参数 name(类型为 string),并返回一个字符串。

函数重载

TypeScript 支持函数重载,即在一个函数名下定义多个函数类型。

function display(value: string): void;
function display(value: number): void;
function display(value: string | number): void {
    console.log(value);
}

display("Hello"); // 输出: Hello
display(42); // 输出: 42

参数类型注解

在函数声明中,可以为参数添加类型注解,以明确参数的预期类型。

// 参数类型注解
function addNumbers(x: number, y: number): number {
    return x + y;
}

在这个示例中,addNumbers 函数接受两个参数 xy,它们的类型都被注解为 number

可选参数和默认参数

在函数声明中,可以使用问号 ? 来表示可选参数,以及使用 = 来指定默认参数值。

// 可选参数和默认参数
function buildName(firstName: string, lastName?: string): string {
    if (lastName) {
        return `${firstName} ${lastName}`;
    } else {
        return firstName;
    }
}

function sayHello(message: string = "Hello"): void {
    console.log(message);
}

在这个示例中,buildName 函数有一个可选参数 lastName,而 sayHello 函数有一个默认参数 message

剩余参数(Rest Parameters)

使用剩余参数语法(以三个点 ... 开头)可以将参数集合到一个数组中。

function mergeWords(...words: string[]): string {
    return words.join(" ");
}

console.log(mergeWords("Hello", "TypeScript", "World")); // 输出: Hello TypeScript World

接口初探:

什么是接口?

在 TypeScript 中,接口是一种用于定义对象形状的抽象类型。接口定义了对象应该具有的属性和方法,从而提供了一种对代码进行约束和组织的机制。

示例:

// 接口定义
interface Person {
    name: string;
    age: number;
}

在这个示例中,Person 接口定义了一个对象应该具有的属性:name(字符串类型)和 age(数字类型)。

可选属性
// 接口中的可选属性
interface Car {
    brand: string;
    model: string;
    year?: number; // 可选属性
}

// 使用接口定义对象
let myCar: Car = { brand: "Toyota", model: "Camry" };

在这个示例中,Car 接口中的 year 属性被标记为可选,对象可以包含或不包含该属性。

使用接口

函数参数类型

接口可以用于约束函数的参数类型,确保函数接受符合特定形状的对象作为参数。

示例:

// 使用接口约束函数参数
interface Person {
    name: string;
    age: number;
}

function printPerson(person: Person): void {
    console.log(`Name: ${person.name}, Age: ${person.age}`);
}

// 调用函数
printPerson({ name: "Alice", age: 28 });

在这个示例中,printPerson 函数接受一个参数 person,其类型被约束为 Person 接口,确保传入的对象具有 nameage 属性。

对象形状的约束

接口还可以用于约束对象的形状,确保对象包含特定的属性和方法。

示例:

// 使用接口约束对象形状
interface Point {
    x: number;
    y: number;
}

// 使用接口定义对象
let point: Point = { x: 10, y: 20 };

在这个示例中,Point 接口约束了对象的形状,确保对象具有 xy 属性。

元组

元组是特殊数组,可包含不同类型元素

元组是 TypeScript 中的一种特殊数组类型,它允许包含不同类型的元素。与数组不同的是,元组中的每个位置都可以具有指定的类型。

示例:

// 元组的声明和初始化
let person: [string, number, boolean];
person = ["Alice", 28, false];

在这个示例中,person 是一个包含字符串、数字和布尔值的元组。

元组类型的声明

可以使用类型注解为元组指定类型,这样在初始化时必须按照指定类型的顺序提供元素。

示例:

// 带类型注解的元组
let employee: [string, number];
employee = ["Bob", 30];

在这个示例中,employee 是一个带有类型注解的元组,包含一个字符串和一个数字。

元组的使用允许你在处理异构数据(不同类型的数据)时更有结构化的方式,但需要小心确保元组的长度和类型与声明一致。

类型断言

类型断言的两种语法

类型断言是一种告诉编译器某个值的类型的方式,有两种语法形式:尖括号语法和 as 语法。

示例:

// 尖括号语法
let value1: any = "Hello, TypeScript!";
let length1: number = (<string>value1).length;

// as 语法
let value2: any = "Hello, TypeScript!";
let length2: number = (value2 as string).length;

在这个示例中,value1value2 的类型被声明为 any,然后使用类型断言将其转换为字符串类型,从而获取字符串的长度。

处理类型不确定的情况

在处理类型不确定的情况下,类型断言可以用于告诉编译器你对值的类型有更好的了解,并且希望使用该类型的属性或方法。

示例:

// 处理类型不确定的情况
function getLength(value: any): number {
    // 使用类型断言获取字符串长度
    return (value as string).length;
}

// 调用函数
let result: number = getLength("Hello, TypeScript!");

在这个示例中,getLength 函数接受一个类型不确定的参数 value,使用类型断言将其转换为字符串类型,然后获取字符串的长度。

类型断言的使用要慎重,确保你对值的类型有足够的了解,以避免运行时错误。

枚举

什么是枚举?

枚举是一种用于定义一组有逻辑关联的常数值的方式,使代码更具可读性和可维护性。枚举成员具有数字或字符串值,用于表示相关的命名常数。

示例:

// 枚举定义
enum Color {
    Red,
    Green,
    Blue,
}

// 使用枚举
let myColor: Color = Color.Green;

在这个示例中,Color 枚举定义了三个成员:RedGreenBlue。枚举成员的值默认为从 0 开始的递增数字。

赋值和手动指定成员的值

可以手动指定枚举成员的值,也可以让 TypeScript 根据默认规则自动赋值。

示例:

// 默认赋值
enum Direction1 {
    North, // 0
    South, // 1
    East,  // 2
    West,  // 3
}

// 手动指定成员的值
enum Direction2 {
    North = 1,  // 1
    South = 2,  // 2
    East = 4,   // 4
    West = 8,   // 8
}

在这个示例中,Direction1 枚举成员的值由默认规则赋值,而 Direction2 枚举成员的值被手动指定。

字符串枚举

字符串枚举是一种枚举类型,其成员使用字符串值而不是默认的数字值。字符串枚举提供了更具可读性的方式来表示一组相关的命名常数。

示例:

// 字符串枚举
enum Direction {
    North = "N",
    South = "S",
    East = "E",
    West = "W",
}

// 使用字符串枚举
let myDirection: Direction = Direction.North;

在这个示例中,Direction 枚举的成员使用字符串值,例如 North 的值为 "N"。字符串枚举增加了可读性,使得代码更加清晰。

面向对象编程

类和对象

类的定义与实例化

在 TypeScript 中,类是一种将属性和方法组合到一起的机制。通过 class 关键字,我们可以定义一个类。类中包含了属性和方法的声明,这些方法可以操作类的属性。

class Animal {
    // 属性
    name: string;

    // 构造函数
    constructor(name: string) {
        this.name = name;
    }

    // 方法
    makeSound(): void {
        console.log("Some generic sound");
    }
}

// 创建类的实例
const dog = new Animal("Dog");
dog.makeSound(); // 输出: Some generic sound

在这个例子中,Animal 类有一个属性 name 和一个方法 makeSound。通过 new Animal("Dog") 创建了一个 Animal 类的实例,并通过 dog.makeSound() 调用了它的方法。

访问修饰符

访问修饰符用于控制类成员的可访问性。在 TypeScript 中,有三种访问修饰符:publicprivateprotected

  • public(默认): 公共成员,可以在类内部和外部访问。
class Person {
    name: string;

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

const person = new Person("John");
console.log(person.name); // 合法,输出: John
  • private: 私有成员,只能在类内部访问。
class Person {
    private age: number;

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

    getAge(): number {
        return this.age; // 合法,在类内部访问私有成员
    }
}

const person = new Person(25);
console.log(person.getAge()); // 合法,通过公共方法访问私有成员
// console.log(person.age); // 错误,不能在外部直接访问私有成员
  • protected: 受保护的成员,可以在类内部和继承的子类中访问。
class Animal {
    protected category: string;

    constructor(category: string) {
        this.category = category;
    }
}

class Dog extends Animal {
    constructor(category: string) {
        super(category);
    }

    displayCategory(): void {
        console.log(`Category: ${this.category}`); // 合法,在子类中访问受保护的成员
    }
}

const dog = new Dog("Mammal");
dog.displayCategory(); // 输出: Category: Mammal
// console.log(dog.category); // 错误,不能在外部直接访问受保护的成员

继承和多态

继承

继承是面向对象编程中的一种重要概念,它允许一个类(子类)继承另一个类(父类)的属性和方法。通过继承,子类可以复用父类的代码,同时可以添加或重写父类的行为。

class Animal {
    // 属性
    name: string;

    // 构造函数
    constructor(name: string) {
        this.name = name;
    }

    // 方法
    makeSound(): void {
        console.log("Some generic sound");
    }
}

// Dog 类继承自 Animal 类
class Dog extends Animal {
    // 子类可以拥有自己的属性和方法
    bark(): void {
        console.log("Woof! Woof!");
    }
}

// 创建 Dog 类的实例
const myDog = new Dog("Buddy");

// 调用父类的方法
myDog.makeSound(); // 继承父类方法,输出: Some generic sound

// 调用子类的方法
myDog.bark(); // 调用子类方法,输出: Woof! Woof!

在这个例子中,Dog 类继承了 Animal 类的属性和方法,同时添加了自己的方法 bark

多态

多态性允许使用一个基类类型的变量来引用一个派生类的对象,实现对不同对象的统一操作。在 TypeScript 中,多态性通常通过方法的重写来实现。

class Cat extends Animal {
    // 重写父类方法
    makeSound(): void {
        console.log("Meow!");
    }
}

// 多态性的应用
const myCat: Animal = new Cat();
myCat.makeSound(); // 多态调用,输出: Meow!

在这个例子中,Cat 类继承了 Animal 类,并重写了父类的 makeSound 方法。通过将 Cat 类的实例赋值给 Animal 类型的变量 myCat,实现了多态性的应用。即使变量的类型是基类,但在运行时会根据实际对象的类型调用相应的方法。

方法重写是指子类重新定义或修改了从父类继承的方法。在上述例子中,Cat 类重写了 Animal 类的 makeSound 方法,以适应猫的特有声音。

继承和多态是面向对象编程中的两个关键概念,通过它们可以建立更加灵活、可扩展的代码结构。

抽象类和接口

抽象类

抽象类是一种不能被实例化的类,通常包含一些抽象方法,需要子类实现这些方法。抽象类通过 abstract 关键字定义。

abstract class Shape {
    // 抽象方法,子类必须实现
    abstract draw(): void;

    // 普通方法
    getInfo(): string {
        return "This is a shape.";
    }
}

class Circle extends Shape {
    // 实现抽象方法
    draw(): void {
        console.log("Drawing a circle");
    }
}

const circle = new Circle();
circle.draw(); // 输出: Drawing a circle
console.log(circle.getInfo()); // 输出: This is a shape.

在上述例子中,Shape 类是一个抽象类,包含一个抽象方法 draw 和一个普通方法 getInfoCircle 类继承自 Shape 类,并实现了 draw 方法。

接口

接口是一种用于定义对象形状的抽象结构,包含属性和方法的声明。类可以实现一个或多个接口,实现接口的类必须提供接口中定义的所有属性和方法。

interface Printable {
    print(): void;
}

class Document implements Printable {
    // 实现接口中的方法
    print(): void {
        console.log("Printing document");
    }
}

在这个例子中,Printable 是一个接口,包含一个方法 printDocument 类实现了 Printable 接口,提供了 print 方法的实现。

抽象类和接口的比较

  • 抽象类:

    • 可以包含抽象方法和普通方法。
    • 可以包含成员变量。
    • 通过 extends 关键字继承。
  • 接口:

    • 只能包含抽象方法和属性的声明,不能包含实现。
    • 不能包含成员变量。
    • 通过 implements 关键字实现。
abstract class Animal {
    abstract makeSound(): void;
    abstract move(): void;

    getInfo(): string {
        return "This is an animal.";
    }
}

interface Eatable {
    eat(): void;
}

class Dog extends Animal implements Eatable {
    makeSound(): void {
        console.log("Woof! Woof!");
    }

    move(): void {
        console.log("Running");
    }

    eat(): void {
        console.log("Eating");
    }
}

在上述例子中,Animal 是一个抽象类,包含抽象方法 makeSoundmove,以及一个普通方法 getInfoEatable 是一个接口,包含抽象方法 eatDog 类同时继承自 Animal 类和实现了 Eatable 接口,提供了所有必需的实现。

抽象类和接口是 TypeScript 中用于实现抽象和组合的两个重要概念,可以帮助构建更灵活、可扩展的代码结构。

模块化开发

为什么需要模块化?

模块化开发是一种将代码划分为独立、可重用的模块或文件的编程方法。在大型软件项目中,模块化开发变得至关重要,有以下几个主要原因:

  1. 可维护性: 将代码拆分成模块后,每个模块都有明确的功能和责任,使得代码更易于维护。开发者可以更容易地理解和修改单个模块,而无需深入整个代码库。

  2. 可重用性: 模块化使得代码可以更容易地被重用。一个良好设计的模块可以在多个地方使用,而无需重复编写相同的代码。这降低了开发的工作量,并提高了代码的一致性。

  3. 命名空间与避免命名冲突: 在大型项目中,存在大量的变量和函数。模块化开发通过将这些变量和函数封装在模块中,创建了一个独立的命名空间。这有助于避免全局命名冲突,提高代码的可靠性。

  4. 依赖管理: 模块化开发使得对外部依赖的管理更加清晰。每个模块可以明确指定自己的依赖关系,而不需要关心其他模块的具体实现。这使得项目的结构更加清晰,降低了代码之间的耦合度。

  5. 并行开发: 在模块化开发中,不同的模块可以由不同的开发团队或开发者独立开发。这种并行开发方式提高了项目的开发效率,因为不同的团队可以专注于各自的模块而不会相互干扰。

  6. 测试和调试: 模块化开发简化了测试和调试过程。由于模块具有清晰的边界,可以更容易地对单个模块进行单元测试,而不需要考虑整个应用的复杂性。

综上所述,模块化开发有助于提高代码的可维护性、可重用性,减少命名冲突,简化依赖管理,支持并行开发,以及简化测试和调试过程。这使得大型软件项目更易于管理和扩展。在现代前端开发中,模块化已经成为一种标准实践,被广泛应用于构建可维护、可扩展的应用程序。

导入和导出模块

在 TypeScript 中,使用 importexport 关键字来实现模块的导入和导出。

示例:

// person.ts

export const name: string = "John";
export function sayHello(): void {
    console.log(`Hello, ${name}!`);
}

export class Person {
    constructor(public age: number) {}

    celebrateBirthday(): void {
        console.log(`Happy Birthday! Age: ${this.age}`);
    }
}
// app.ts

// 导入整个模块
import * as PersonModule from "./person";

// 使用导出的内容
console.log(PersonModule.name); // 输出: John
PersonModule.sayHello(); // 输出: Hello, John!

const john = new PersonModule.Person(30);
john.celebrateBirthday(); // 输出: Happy Birthday! Age: 30

通过 import 导入整个模块或通过 { name, sayHello, Person } 导入模块中指定的部分。

使用命名空间

命名空间是一种在全局作用域内组织代码的方式,它将一系列的变量、函数和类封装在一个命名空间中,以避免全局作用域的污染。

示例:

// shapes.ts

namespace Shapes {
    export class Circle {
        constructor(public radius: number) {}

        area(): number {
            return Math.PI * this.radius * this.radius;
        }
    }

    export class Rectangle {
        constructor(public width: number, public height: number) {}

        area(): number {
            return this.width * this.height;
        }
    }
}
// app.ts

// 使用命名空间中的内容
const myCircle = new Shapes.Circle(5);
console.log(myCircle.area()); // 输出: 78.54

const myRectangle = new Shapes.Rectangle(4, 6);
console.log(myRectangle.area()); // 输出: 24

在上述例子中,Shapes 是一个命名空间,包含了 CircleRectangle 两个类。在 app.ts 文件中,通过 Shapes.CircleShapes.Rectangle 使用了命名空间中的内容。

使用命名空间可以有效地组织代码,避免命名冲突,并提高代码的可读性。然而,在现代 TypeScript 中,使用模块更为推荐,因为它提供了更强大的封装和导入导出功能。

TypeScript 高级特性

泛型(Generics)

什么是泛型?

  • 概念和作用:

    • 泛型是 TypeScript 中一种强大的特性,允许在编写函数、类、接口时使用参数化类型。具体来说,可以在定义时使用占位符表示类型,然后在使用时动态指定具体的类型。
    • 作用: 主要用于提高代码的灵活性和重用性,使得函数、类、接口等可以处理多种数据类型而不失去类型检查的优势。
  • 为什么需要使用泛型?

    • 通用性: 泛型使得代码可以同时处理多种类型的数据,而不是针对特定类型写死代码,从而提高代码的通用性。
    • 类型安全: 在使用时指定具体类型,使得在编译阶段就能发现类型错误,提高了代码的类型安全性。
    • 代码重用: 可以编写更灵活、可复用的函数和组件,适应不同类型的数据,避免了代码的冗余。

泛型是 TypeScript 中的一项关键特性,它在让代码更加灵活和通用的同时,保持了类型检查的优势,为开发者提供了一种强大的工具来处理各种数据类型。

泛型函数

  • 创建和使用泛型函数:

    • 在函数名后面使用 <T> 或其他标识符表示泛型参数。
    • 示例:
      function identity<T>(arg: T): T {
          return arg;
      }
      
  • 泛型参数和返回值:

    • 泛型参数 <T> 可以用于函数的参数类型和返回值类型。
    • 示例:
      function pair<T, U>(first: T, second: U): [T, U] {
          return [first, second];
      }
      
  • 使用泛型函数:

    • 在调用泛型函数时,可以明确指定具体的类型,也可以让 TypeScript 推断类型。
    • 示例:
      const result1 = identity<string>('Hello');
      const result2 = identity(42); // TypeScript 可以推断出类型为 number
      
      const tupleResult = pair('one', 1); // 类型为 [string, number]
      

泛型函数允许我们编写与数据类型无关的函数,提高了代码的通用性。通过灵活指定类型参数,可以适应不同类型的输入数据,同时保持了类型安全。

泛型类

  • 在类中使用泛型:

    • 类本身可以是泛型的,也可以在类的方法中使用泛型。
    • 示例:
      class Box<T> {
          private value: T;
      
          constructor(value: T) {
              this.value = value;
          }
      
          getValue(): T {
              return this.value;
          }
      }
      
  • 泛型类的应用场景:

    • 容器类: 适用于需要存储或操作不同类型数据的容器,如数组、栈、队列等。

      const stringBox = new Box<string>('TypeScript');
      const numberBox = new Box<number>(42);
      
      console.log(stringBox.getValue()); // 输出 'TypeScript'
      console.log(numberBox.getValue()); // 输出 42
      
    • 工具类: 适用于需要提供通用工具方法的类,能够处理不同类型数据的工具逻辑。

      class MathOperations<T extends number | string> {
          add(a: T, b: T): T {
              if (typeof a === 'number' && typeof b === 'number') {
                  return a + b as T;
              } else if (typeof a === 'string' && typeof b === 'string') {
                  return a + b as T;
              } else {
                  throw new Error('Unsupported types for addition.');
              }
          }
      }
      
      const mathOps = new MathOperations();
      console.log(mathOps.add(5, 3));      // 输出 8
      console.log(mathOps.add('Hello', ' TypeScript'));  // 输出 'Hello TypeScript'
      

泛型类提供了一种灵活的方式来创建通用的、可复用的类,能够处理多种数据类型,从而提高代码的通用性和可维护性。

泛型约束

  • 如何对泛型进行约束?

    • 使用 extends 关键字约束泛型类型范围,确保泛型类型满足一定的条件。
    • 示例:
      interface Lengthwise {
          length: number;
      }
      
      function loggingIdentity<T extends Lengthwise>(arg: T): T {
          console.log(arg.length);
          return arg;
      }
      
  • 泛型约束的实际应用:

    • 确保对象具有特定属性:

      interface Named {
          name: string;
      }
      
      function displayName<T extends Named>(obj: T): void {
          console.log(obj.name);
      }
      
    • 确保对象具有特定方法:

      interface Printable {
          print(): void;
      }
      
      function printObject<T extends Printable>(obj: T): void {
          obj.print();
      }
      
    • 数组元素的类型约束:

      function printLength<T extends { length: number }>(arr: T[]): void {
          arr.forEach(item => console.log(item.length));
      }
      

泛型约束使得我们可以对泛型参数的类型范围进行限制,确保在函数内部能够安全地访问特定的属性或方法。这提高了代码的类型安全性,同时保持了泛型的灵活性。

高级类型(Advanced Types)

联合类型(Union Types)

  • 什么是联合类型?

    • 联合类型是 TypeScript 中一种高级类型,允许变量具有多种可能的类型。
    • 示例:
      let variable: number | string;
      variable = 42;      // 可以是 number 类型
      variable = 'Hello'; // 也可以是 string 类型
      
  • 如何处理多种类型的情况?

    • 使用 |(竖线)操作符将多种类型联合在一起。
    • 可以使用条件语句、类型判断等方式在运行时处理不同类型的情况。
    • 示例:
      function displayType(value: number | string): void {
          if (typeof value === 'number') {
              console.log('It is a number.');
          } else {
              console.log('It is a string.');
          }
      }
      
      displayType(42);      // 输出 'It is a number.'
      displayType('Hello'); // 输出 'It is a string.'
      

联合类型提供了一种灵活的方式,使得变量可以包含多种类型的值。在处理不同类型的情况时,可以使用类型判断等手段进行更灵活的操作。

交叉类型(Intersection Types)

  • 什么是交叉类型?

    • 交叉类型是 TypeScript 中一种高级类型,允许将多个类型合并为一个类型。
    • 示例:
      interface Dog {
          bark(): void;
      }
      
      interface Bird {
          fly(): void;
      }
      
      type DogAndBird = Dog & Bird;
      
  • 如何将多个类型合并为一个类型?

    • 使用 &(与)操作符将多个类型交叉在一起。
    • 合并后的类型将包含所有原始类型的成员。
    • 示例:
      function petAction(pet: DogAndBird): void {
          pet.bark(); // 具有 Dog 接口的方法
          pet.fly();  // 具有 Bird 接口的方法
      }
      
      const myPet: DogAndBird = {
          bark: () => console.log('Woof!'),
          fly: () => console.log('Flap!'),
      };
      
      petAction(myPet);
      

交叉类型允许创建具有多种类型成员的新类型,适用于需要合并多个类型特性的场景。在这个例子中,DogAndBird 类型具有同时包含了 DogBird 接口的成员。

条件类型(Conditional Types)

  • 条件类型的语法和使用:

    • 条件类型在 TypeScript 中使用 infer 关键字来引入一种延迟推断的机制。

    • 语法:

      type MyType<T> = T extends SomeType ? TrueType : FalseType;
      
    • 示例:

      type IsString<T> = T extends string ? true : false;
      
      const isString: IsString<number> = false;   // 类型是 false
      const isAnotherString: IsString<string> = true; // 类型是 true
      
  • 实际场景中的应用示例:

    • 假设我们有一个函数,如果传入的参数是对象,就返回对象的值的类型,否则返回参数本身的类型:

      type Unbox<T> = T extends { value: infer U } ? U : T;
      
      const stringValue: Unbox<{ value: 'text' }> = 'text'; // 类型是 'text'
      const numberValue: Unbox<number> = 42;              // 类型是 number
      
    • 在上述例子中,Unbox<T> 条件类型用于检测 T 是否是包含 { value: infer U } 结构的对象,如果是,则返回 U,否则返回 T

条件类型常用于编写更具灵活性的泛型工具,能够根据输入的类型动态地进行类型转换或推断。

映射类型(Mapped Types)

  • 映射类型的基本概念:

    • 映射类型是 TypeScript 中一种强大的工具,用于创建新类型,从而修改或转换现有类型的属性。
    • 使用 in 关键字和 keyof 操作符进行映射。
    • 示例:
      type Flags = {
          option1: boolean;
          option2: boolean;
      };
      
      type NullableFlags = { [K in keyof Flags]: boolean | null };
      
  • 如何通过映射类型进行类型转换和修改?

    • 使用 in 关键字和 keyof 操作符,结合泛型和条件类型,可以实现对属性的动态修改和转换。
    • 示例:
      type UppercaseProps<T> = { [K in keyof T]: T[K] extends string ? Uppercase<T[K]> : T[K] };
      
      interface Person {
          name: string;
          age: number;
          city: string;
      }
      
      type PersonWithUppercaseName = UppercaseProps<Person>;
      

在这个例子中,UppercaseProps<T> 映射类型用于将传入的类型 T 的字符串属性转换为大写形式。映射类型是 TypeScript 中用于处理现有类型的强大工具,通过动态生成新类型,可以在很大程度上提高代码的灵活性。

其他类型

Never 类型
never 类型的应用场景

never 类型表示永远不会返回结果的表达式,常用于处理异常和不可达代码。

示例:

// 使用 never 处理异常
function throwError(message: string): never {
    throw new Error(message);
}

// 使用 never 处理不可达代码
function infiniteLoop(): never {
    while (true) {
        // 无限循环,不可达代码
    }
}

在这个示例中,throwError 函数抛出异常,其返回类型被标记为 never,因为该函数永远不会正常返回。同样,infiniteLoop 函数是一个无限循环,其返回类型也是 never

类型守卫
使用类型守卫进行类型推断

类型守卫是一种在特定的作用域内判断变量类型的方法,通过它可以缩小变量的类型范围,提高类型安全性。

示例:

// 使用 typeof 类型守卫
function printValue(value: string | number): void {
    if (typeof value === "string") {
        console.log(value.toUpperCase());
    } else {
        console.log(value.toFixed(2));
    }
}

// 使用 instanceof 类型守卫
class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
}

class Bird extends Animal {
    fly(): void {
        console.log(`${this.name} is flying.`);
    }
}

function printAnimalInfo(animal: Animal): void {
    if (animal instanceof Bird) {
        animal.fly();
    } else {
        console.log(`${animal.name} is not a bird.`);
    }
}

在这个示例中,printValue 函数通过 typeof 类型守卫判断 value 的类型,从而执行不同的操作。而 printAnimalInfo 函数通过 instanceof 类型守卫判断 animal 是否为 Bird 类型,进而执行相应的操作。

类型守卫可以让 TypeScript 在特定情况下更好地理解变量的类型,减少潜在的类型错误。

装饰器(Decorators)

装饰器基础

  • 什么是装饰器?

    • 装饰器是 TypeScript 中一种实验性质的特性,用于添加注释、元数据或修改类和类成员的行为。它借鉴了装饰器模式的概念。
    • 装饰器是一种特殊类型的声明,可附加到类声明、方法、访问器、属性或参数上。
  • 装饰器的基本语法和用途:

    • 类装饰器:

      • 用于在类声明之前被声明。
      • 接受一个参数,即类的构造函数。
      • 示例:
        function classDecorator(constructor: Function) {
            console.log('Class is decorated!');
        }
        
        @classDecorator
        class ExampleClass {
            // class logic
        }
        
    • 方法装饰器:

      • 用于声明在方法声明之前被声明。
      • 接受三个参数:类的原型、方法名和方法的属性描述符。
      • 示例:
        function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
            console.log(`Method ${propertyKey} is decorated!`);
        }
        
        class ExampleClass {
            @methodDecorator
            exampleMethod() {
                // method logic
            }
        }
        
    • 属性装饰器:

      • 用于声明在属性声明之前被声明。
      • 接受两个参数:类的原型和属性名。
      • 示例:
        function propertyDecorator(target: any, propertyKey: string) {
            console.log(`Property ${propertyKey} is decorated!`);
        }
        
        class ExampleClass {
            @propertyDecorator
            exampleProperty: string;
        }
        
    • 参数装饰器:

      • 用于声明在参数声明之前被声明。
      • 接受三个参数:类的原型、方法名和参数在函数参数列表中的索引。
      • 示例:
        function parameterDecorator(target: any, propertyKey: string, parameterIndex: number) {
            console.log(`Parameter at index ${parameterIndex} is decorated!`);
        }
        
        class ExampleClass {
            exampleMethod(@parameterDecorator param1: string, @parameterDecorator param2: number) {
                // method logic
            }
        }
        

装饰器是 TypeScript 提供的一种元编程的手段,通过它可以在不修改源代码的情况下对类及其成员进行增强。在实际应用中,装饰器经常与其他元编程特性和设计模式结合使用,提高了代码的可维护性和可扩展性。

类装饰器

  • 创建和应用类装饰器:

    • 创建类装饰器:

      • 类装饰器是一个函数,接收类的构造函数作为参数。
      • 示例:
        function classDecorator(constructor: Function) {
            console.log('Class is decorated!');
        }
        
    • 应用类装饰器:

      • 在类声明前使用 @ 符号加上类装饰器。
      • 示例:
        @classDecorator
        class ExampleClass {
            // class logic
        }
        
  • 类装饰器的高级应用:

    • 修改类的构造函数:

      • 可以在类装饰器中修改类的构造函数。
      • 示例:
        function modifyConstructor(constructor: Function) {
            return class extends constructor {
                modifiedProperty = 'Modified!';
            };
        }
        
        @modifyConstructor
        class ExampleClass {
            originalProperty = 'Original!';
        }
        
        const instance = new ExampleClass();
        console.log(instance.originalProperty); // 输出 'Original!'
        console.log(instance.modifiedProperty); // 输出 'Modified!'
        
    • 装饰器工厂:

      • 可以通过返回函数的方式创建装饰器工厂,实现更灵活的装饰器。
      • 示例:
        function decoratorFactory(message: string) {
            return function classDecorator(constructor: Function) {
                console.log(`${message}: Class is decorated!`);
            };
        }
        
        @decoratorFactory('Custom Message')
        class ExampleClass {
            // class logic
        }
        
    • 多个装饰器的执行顺序:

      • 多个装饰器按照从上到下的顺序依次执行。
      • 示例:
        function firstDecorator(constructor: Function) {
            console.log('First decorator');
        }
        
        function secondDecorator(constructor: Function) {
            console.log('Second decorator');
        }
        
        @firstDecorator
        @secondDecorator
        class ExampleClass {
            // class logic
        }
        // 输出:
        // Second decorator
        // First decorator
        

类装饰器是一种强大的工具,能够在类声明之前对类进行增强或修改。通过组合多个装饰器,可以实现更丰富的功能,如日志记录、权限控制等。

方法装饰器

  • 创建和应用方法装饰器:

    • 创建方法装饰器:

      • 方法装饰器是一个函数,接收三个参数:类的原型、方法名和方法的属性描述符。
      • 示例:
        function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
            console.log(`Method ${propertyKey} is decorated!`);
        }
        
    • 应用方法装饰器:

      • 在方法声明前使用 @ 符号加上方法装饰器。
      • 示例:
        class ExampleClass {
            @methodDecorator
            exampleMethod() {
                // method logic
            }
        }
        
  • 方法装饰器的实际应用:

    • 日志记录:

      • 可以使用方法装饰器记录方法的调用信息。
      • 示例:
        function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
            const originalMethod = descriptor.value;
        
            descriptor.value = function (...args: any[]) {
                console.log(`Calling method ${propertyKey} with arguments: ${args.join(', ')}`);
                const result = originalMethod.apply(this, args);
                console.log(`Method ${propertyKey} returned: ${result}`);
                return result;
            };
        
            return descriptor;
        }
        
        class ExampleClass {
            @logMethod
            exampleMethod(message: string) {
                console.log(`Executing method with message: ${message}`);
            }
        }
        
        const instance = new ExampleClass();
        instance.exampleMethod('Hello, TypeScript!');
        
    • 权限控制:

      • 可以使用方法装饰器实现权限控制逻辑。
      • 示例:
        function checkPermission(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
            const originalMethod = descriptor.value;
        
            descriptor.value = function (...args: any[]) {
                if (this.hasPermission()) {
                    return originalMethod.apply(this, args);
                } else {
                    console.log('Permission denied!');
                }
            };
        
            return descriptor;
        }
        
        class ExampleClass {
            private hasPermission(): boolean {
                // Check permission logic
                return true;
            }
        
            @checkPermission
            sensitiveOperation() {
                console.log('Executing sensitive operation...');
            }
        }
        

方法装饰器在实际应用中常用于处理横切关注点,如日志记录、权限控制等。通过在方法执行前或执行后插入逻辑,可以提高代码的可维护性和可拓展性。

属性装饰器

  • 创建和应用属性装饰器:

    • 创建属性装饰器:

      • 属性装饰器是一个函数,接收两个参数:类的原型和属性名。
      • 示例:
        function propertyDecorator(target: any, propertyKey: string) {
            console.log(`Property ${propertyKey} is decorated!`);
        }
        
    • 应用属性装饰器:

      • 在属性声明前使用 @ 符号加上属性装饰器。
      • 示例:
        class ExampleClass {
            @propertyDecorator
            exampleProperty: string;
        }
        
  • 属性装饰器的使用场景:

    • 属性校验:

      • 可以使用属性装饰器进行属性值的校验。
      • 示例:
        function validateStringLength(minLength: number, maxLength: number) {
            return function (target: any, propertyKey: string) {
                let value: string;
        
                Object.defineProperty(target, propertyKey, {
                    get: () => value,
                    set: (newValue: string) => {
                        if (newValue.length >= minLength && newValue.length <= maxLength) {
                            value = newValue;
                        } else {
                            console.log(`Invalid length for ${propertyKey}`);
                        }
                    },
                    enumerable: true,
                    configurable: true,
                });
            };
        }
        
        class User {
            @validateStringLength(3, 10)
            username: string = '';
        }
        
    • 日志记录:

      • 属性装饰器也可用于记录属性的读取和赋值操作。
      • 示例:
        function logPropertyAccess(target: any, propertyKey: string) {
            let value: any;
        
            Object.defineProperty(target, propertyKey, {
                get: () => {
                    console.log(`Getting value of ${propertyKey}`);
                    return value;
                },
                set: (newValue: any) => {
                    console.log(`Setting value of ${propertyKey} to ${newValue}`);
                    value = newValue;
                },
                enumerable: true,
                configurable: true,
            });
        }
        
        class ExampleClass {
            @logPropertyAccess
            exampleProperty: string = 'Initial Value';
        }
        

属性装饰器在一些特定场景下非常有用,例如属性值的校验、属性访问的日志记录等。通过修改属性的定义,可以在属性的读取和赋值时插入自定义逻辑。

参数装饰器

  • 创建和应用参数装饰器:

    • 创建参数装饰器:

      • 参数装饰器是一个函数,接收三个参数:类的原型、方法名和参数在函数参数列表中的索引。
      • 示例:
        function parameterDecorator(target: any, propertyKey: string, parameterIndex: number) {
            console.log(`Parameter at index ${parameterIndex} is decorated!`);
        }
        
    • 应用参数装饰器:

      • 在方法的参数声明前使用 @ 符号加上参数装饰器。
      • 示例:
        class ExampleClass {
            exampleMethod(@parameterDecorator param1: string, @parameterDecorator param2: number) {
                // method logic
            }
        }
        
  • 参数装饰器的使用场景:

    • 参数校验:

      • 参数装饰器可以用于对方法参数的值进行校验。
      • 示例:
        function validateParameterLength(target: any, propertyKey: string, parameterIndex: number) {
            const originalMethod = target[propertyKey];
        
            target[propertyKey] = function (...args: any[]) {
                const parameterValue = args[parameterIndex];
        
                if (typeof parameterValue === 'string' && parameterValue.length > 5) {
                    console.log(`Parameter at index ${parameterIndex} has valid length`);
                    return originalMethod.apply(this, args);
                } else {
                    console.log(`Invalid length for parameter at index ${parameterIndex}`);
                }
            };
        }
        
        class ExampleClass {
            @validateParameterLength
            exampleMethod(param1: string, param2: string) {
                // method logic
            }
        }
        
    • 日志记录:

      • 参数装饰器也可以用于记录方法参数的值。
      • 示例:
        function logParameterValue(target: any, propertyKey: string, parameterIndex: number) {
            const originalMethod = target[propertyKey];
        
            target[propertyKey] = function (...args: any[]) {
                const parameterValue = args[parameterIndex];
                console.log(`Value of parameter at index ${parameterIndex}: ${parameterValue}`);
                return originalMethod.apply(this, args);
            };
        }
        
        class ExampleClass {
            @logParameterValue
            exampleMethod(param1: string, param2: number) {
                // method logic
            }
        }
        

参数装饰器在一些特定场景下非常有用,例如参数值的校验、参数访问的日志记录等。通过修改方法的定义,可以在方法执行前插入自定义逻辑。

其他高级特性

可辨识联合(Discriminated Unions)

  • 什么是可辨识联合?

    • 可辨识联合是 TypeScript 中一种通过具有共同字段的单例类型来保护联合类型的技术。
    • 它通过在联合类型中引入共同的属性,称为“标签”或“discriminant”,来区分不同的成员类型。
  • 如何利用可辨识联合提高代码的安全性?

    • 示例:
      interface Square {
          kind: 'square';
          size: number;
      }
      
      interface Circle {
          kind: 'circle';
          radius: number;
      }
      
      interface Triangle {
          kind: 'triangle';
          sideLength: number;
      }
      
      type Shape = Square | Circle | Triangle;
      
      function getArea(shape: Shape): number {
          switch (shape.kind) {
              case 'square':
                  return shape.size * shape.size;
              case 'circle':
                  return Math.PI * shape.radius * shape.radius;
              case 'triangle':
                  return (Math.sqrt(3) / 4) * shape.sideLength * shape.sideLength;
              default:
                  // TypeScript 提供了 Exhaustiveness Checking,确保我们处理了所有可能的情况
                  const _exhaustiveCheck: never = shape;
                  return _exhaustiveCheck;
          }
      }
      
      const square: Square = { kind: 'square', size: 5 };
      const circle: Circle = { kind: 'circle', radius: 3 };
      const triangle: Triangle = { kind: 'triangle', sideLength: 4 };
      
      console.log(getArea(square));   // 输出 25
      console.log(getArea(circle));   // 输出 ~28.27
      console.log(getArea(triangle)); // 输出 ~6.93
      

在上述示例中,Shape 是一个联合类型,每个成员都有一个共同的字段 kind,用于标识具体的类型。在 getArea 函数中,通过使用 switch 语句根据 kind 来处理不同类型的形状,确保了在代码中处理了所有可能的情况,提高了代码的安全性。如果添加了新的形状类型,TypeScript 会提醒我们更新 switch 语句以处理新类型,避免了遗漏的问题。

明确赋值断言(Definite Assignment Assertion)

  • 为什么需要明确赋值断言?

    • TypeScript 引入了“明确赋值断言”(Definite Assignment Assertion)来解决一些特定情况下的赋值问题。
    • 在某些情况下,TypeScript 无法确定变量是否已经被赋值,这可能导致编译错误。
  • 如何在可能为空的情况下使用断言?

    • 示例:
      let username: string;
      
      if (Math.random() > 0.5) {
          username = 'Alice';
      }
      
      // 此处 TypeScript 会报错,因为 TypeScript 无法确定 username 是否已被赋值
      // const length = username.length; // Error: Object is possibly 'undefined'.
      
      // 在可能为空的情况下使用断言:
      const length = username!.length; // 使用 ! 断言,告诉 TypeScript 我们确信 username 不为空
      
      console.log(length);
      

在上述示例中,由于 username 的赋值是根据随机条件的,TypeScript 无法确定在后续的代码中 username 是否已被赋值。此时,我们可以使用明确赋值断言(!)告诉 TypeScript 我们确信 username 不为空,从而避免编译错误。

需要注意的是,使用明确赋值断言时,我们需要确保变量确实在使用前被正确赋值,否则可能会导致运行时错误。因此,使用这种断言时要格外小心,确保对代码的了解和控制。

面向对象设计模式在 TypeScript 中的应用

  • 如何使用 TypeScript 实现常见的面向对象设计模式?

    • 单例模式(Singleton Pattern):

      • 保证一个类只有一个实例,并提供一个全局访问点。
      class Singleton {
          private static instance: Singleton;
      
          private constructor() {}
      
          public static getInstance(): Singleton {
              if (!Singleton.instance) {
                  Singleton.instance = new Singleton();
              }
              return Singleton.instance;
          }
      
          public showMessage(): void {
              console.log("Hello, Singleton!");
          }
      }
      
      const singleton1 = Singleton.getInstance();
      const singleton2 = Singleton.getInstance();
      
      console.log(singleton1 === singleton2); // 输出 true,表示是同一个实例
      
    • 观察者模式(Observer Pattern):

      • 定义对象间一对多的依赖关系,使得当一个对象改变状态时,所有依赖它的对象都会收到通知并自动更新。
      interface Observer {
          update(message: string): void;
      }
      
      class ConcreteObserver implements Observer {
          constructor(private name: string) {}
      
          update(message: string): void {
              console.log(`${this.name} received message: ${message}`);
          }
      }
      
      class Subject {
          private observers: Observer[] = [];
      
          public addObserver(observer: Observer): void {
              this.observers.push(observer);
          }
      
          public removeObserver(observer: Observer): void {
              const index = this.observers.indexOf(observer);
              if (index !== -1) {
                  this.observers.splice(index, 1);
              }
          }
      
          public notifyObservers(message: string): void {
              this.observers.forEach(observer => observer.update(message));
          }
      }
      
      const observer1 = new ConcreteObserver("Observer 1");
      const observer2 = new ConcreteObserver("Observer 2");
      
      const subject = new Subject();
      subject.addObserver(observer1);
      subject.addObserver(observer2);
      
      subject.notifyObservers("Hello, Observers!");
      
  • 设计模式在实际项目中的应用示例:

    • 应用场景:

      • 在一个电商系统中,购物车模块使用观察者模式,当用户在购物车中添加或删除商品时,通知其他相关模块(如价格显示模块、优惠券模块等)进行更新。
    • 实现示例:

      interface CartObserver {
          updateTotalPrice(totalPrice: number): void;
      }
      
      class ShoppingCart {
          private items: { name: string; price: number }[] = [];
          private observers: CartObserver[] = [];
      
          public addItem(item: { name: string; price: number }): void {
              this.items.push(item);
              this.notifyObservers();
          }
      
          public removeItem(item: { name: string; price: number }): void {
              const index = this.items.findIndex(i => i.name === item.name);
              if (index !== -1) {
                  this.items.splice(index, 1);
                  this.notifyObservers();
              }
          }
      
          public addObserver(observer: CartObserver): void {
              this.observers.push(observer);
          }
      
          public removeObserver(observer: CartObserver): void {
              const index = this.observers.indexOf(observer);
              if (index !== -1) {
                  this.observers.splice(index, 1);
              }
          }
      
          private calculateTotalPrice(): number {
              return this.items.reduce((total, item) => total + item.price, 0);
          }
      
          private notifyObservers(): void {
              const totalPrice = this.calculateTotalPrice();
              this.observers.forEach(observer => observer.updateTotalPrice(totalPrice));
          }
      }
      
      class PriceDisplay implements CartObserver {
          public updateTotalPrice(totalPrice: number): void {
              console.log(`Total Price Updated: $${totalPrice.toFixed(2)}`);
          }
      }
      
      const shoppingCart = new ShoppingCart();
      const priceDisplay = new PriceDisplay();
      
      shoppingCart.addObserver(priceDisplay);
      
      shoppingCart.addItem({ name: 'Product A', price: 20 });
      shoppingCart.addItem({ name: 'Product B', price: 30 });
      shoppingCart.removeItem({ name: 'Product A', price: 20 });
      

在实际项目中,设计模式可以帮助我们更好地组织和扩展代码,提高代码的可维护性和可读性。在购物车模块中使用观察者模式,可以很容易地实现对购物车状态的监听,并在状态发生变化时通知其他模块进行相应更新。

总结与展望

回顾整个学习过程,我们不仅从零开始学习了 TypeScript 的基础知识,还深入了解了一些高级特性。在学习的路上,我们克服了一些挑战,应用所学知识到了实际项目中。

对于 TypeScript 的未来趋势,我们展望着它不断演进的语言特性、蓬勃发展的生态系统,以及在前端技术栈中的日益重要的地位。我们鼓励大家保持持续学习的态度,不断实践和创新,参与技术社区,分享经验,共同推动前端技术的发展。

最后,希望这篇文章为前端开发新手提供了一条清晰的学习之路,激发了大家对 TypeScript 的兴趣,引导大家在前端领域迈出坚实的步伐。愿大家在不断学习的过程中,收获更多技能和成就。前路漫漫,让我们共同努力,迎接更多挑战和机遇!

转载自:https://juejin.cn/post/7312736427326029860
评论
请登录