TypeScript笔记 | 青训营

TypeScript笔记 | 青训营

TypeScript 简介

TypeScript 是一种开源的编程语言,它是 JavaScript 的一个超集。它添加了静态类型系统和一些其他功能,使得在开发大型应用时更加可靠和易于维护。TypeScript 最终会被编译成普通的 JavaScript 代码,因此可以在任何支持 JavaScript 的地方运行。

主要特性

  1. 静态类型系统: TypeScript 强调变量类型的声明和类型检查,这使得在开发过程中能够捕获一些常见的错误,减少运行时错误。

  2. 类型注解: 在 TypeScript 中,你可以为变量、函数参数和返回值等添加类型注解,明确表明其所期望的数据类型。

  3. 类和接口: TypeScript 支持面向对象编程,可以定义类和接口,从而提供更丰富的类型描述和代码结构。

  4. 泛型: TypeScript 允许你创建可重用的组件,适用于多种数据类型。通过泛型,你可以编写更通用和类型安全的代码。

  5. 枚举: TypeScript 引入了枚举类型,让你可以定义一组命名的常量值,提高代码的可读性。

  6. 命名空间和模块: TypeScript 支持命名空间和模块化开发,使得代码的组织和分离变得更加简单。

  7. 装饰器: 装饰器是一种特殊的声明,可以附加到类、方法、属性等上,用于自定义它们的行为。在框架和库的开发中特别有用。

安装和使用

  1. 安装 TypeScript: 使用 npm 或 yarn 安装 TypeScript 编译器。

    bash 复制代码
    npm install -g typescript
  2. 创建 TypeScript 文件: 创建一个 .ts 后缀的文件,开始编写 TypeScript 代码。

  3. 编写代码: 使用 TypeScript 的语法和特性编写代码,可以使用类型注解来指定变量的类型。

  4. 编译代码: 使用 TypeScript 编译器将 TypeScript 代码转换为 JavaScript 代码。

    bash 复制代码
    tsc your-file.ts

示例代码

下面是一个简单的 TypeScript 示例,展示了类型注解和类的使用:

typescript 复制代码
// 类型注解
let greeting: string = "Hello, TypeScript!";

// 类的定义
class Person {
    private name: string;

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

    sayHello() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

// 创建实例
const person = new Person("Alice");
person.sayHello();

静态类型系统是一种编程语言特性,用于在代码编写阶段检查变量、函数和表达式的数据类型,以捕获潜在的类型错误,提供更强大的类型安全性。与动态类型系统不同,静态类型系统在编译时进行类型检查,而不是在运行时。

以下是静态类型系统的一些关键概念和优势:

静态类型系统

类型声明

在静态类型系统中,你需要为变量、函数参数、函数返回值等明确地指定所期望的数据类型。这通常通过类型注解或类型声明来完成。

typescript 复制代码
// 使用类型注解声明变量的类型
let age: number = 25;

// 函数参数和返回值的类型声明
function add(a: number, b: number): number {
    return a + b;
}

类型检查

静态类型系统会在编译时检查代码,确保变量和函数的使用与其声明的数据类型相符合。如果存在不匹配的类型,编译器会报告错误。

typescript 复制代码
let name: string = "Alice";
// 下面这行代码会引发类型错误,因为数字不能赋值给字符串类型的变量
name = 42; // Error: Type 'number' is not assignable to type 'string'.

类型推断

静态类型系统还支持类型推断,即在某些情况下,编译器可以自动推断变量的类型,无需显式注解。

typescript 复制代码
let age = 25; // TypeScript 推断 age 的类型为 number

优势

  1. 类型安全性: 静态类型系统能够在编译时捕获一些常见的类型错误,避免在运行时出现难以追踪的错误。

  2. 可读性和维护性: 类型注解使代码更易读懂,因为它们提供了关于变量和函数意图的明确信息。在大型项目中,静态类型系统还能够帮助团队成员理解和修改代码。

  3. 自文档化: 类型注解可以充当文档,帮助开发者了解代码的预期用法和输入输出。

  4. 智能编码助手: 静态类型系统能够让代码编辑器提供更准确的代码补全、错误提示和重构建议。

动态类型 vs. 静态类型

动态类型语言(如 JavaScript)在运行时进行类型检查,因此具有更大的灵活性,但在某些情况下可能导致类型相关的错误只在运行时才能发现。

静态类型语言(如 TypeScript、Java、C++)则在编译时执行类型检查,提供了更高的类型安全性,但需要在代码中显式声明和管理类型。

选择使用哪种类型系统取决于项目的需求、团队的偏好和开发的目标。

类型注解

类型注解是一种在代码中明确指定变量、函数参数、函数返回值等的数据类型的方式。在静态类型系统中,类型注解能够帮助编译器进行类型检查,以确保代码中的数据类型使用是正确的。

在 TypeScript 中,使用冒号(:)后跟一个类型名称来添加类型注解。下面是一些关于类型注解的例子和解释:

变量的类型注解

typescript 复制代码
let age: number; // 声明一个名为 "age" 的变量,它的类型被注解为 number
let name: string; // 声明一个名为 "name" 的变量,它的类型被注解为 string

age = 25; // 合法,age 被赋值为一个 number
name = "Alice"; // 合法,name 被赋值为一个 string

age = "twenty-five"; // 错误,无法将字符串赋值给一个被注解为 number 的变量

函数的类型注解

typescript 复制代码
function add(a: number, b: number): number {
    return a + b;
}

let result = add(10, 20); // result 被推断为 number,因为 add 函数的返回类型被注解为 number

函数参数的类型注解

typescript 复制代码
function greet(name: string): void {
    console.log(`Hello, ${name}!`);
}

greet("Alice"); // 合法,传入一个 string 类型的参数
greet(42); // 错误,无法将数字赋值给一个要求 string 类型的参数

类属性和方法的类型注解

typescript 复制代码
class Person {
    name: string; // 类属性的类型注解

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

    sayHello(): void {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

类型注解不仅可以在变量和函数中使用,还可以用于类的属性和方法。它们提供了代码中数据类型的明确表示,有助于编译器检查类型相关的错误,提高了代码的可读性和可维护性。需要注意的是,类型注解仅在编译时有意义,运行时并不会影响代码的行为。

类和接口

在 TypeScript 中,类(Class)和接口(Interface)是两个重要的概念,用于构建面向对象的代码和实现抽象数据类型。它们分别用于创建对象和定义对象的外部形状。让我们深入了解一下类和接口的概念以及如何在 TypeScript 中使用它们。

类(Class)

类是一种将数据和行为组合在一起的结构,用于创建具有相似特征和功能的对象。它是面向对象编程的核心概念之一。

定义类:

typescript 复制代码
class Person {
    name: string;
    age: number;

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

    sayHello() {
        console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }
}

创建实例:

typescript 复制代码
const person = new Person("Alice", 25);
person.sayHello(); // 输出:Hello, my name is Alice and I'm 25 years old.

接口(Interface)

接口是一种描述对象的外部形状的方式,它定义了对象应该具有的属性和方法。接口在 TypeScript 中用于声明一组规范,而不是实现。

定义接口:

typescript 复制代码
interface Shape {
    area(): number;
}

class Circle implements Shape {
    radius: number;

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

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

使用接口:

typescript 复制代码
const circle = new Circle(5);
console.log(circle.area()); // 输出:78.53981633974483

类与接口的比较

  1. 类: 类用于创建对象,包含属性和方法的定义,可以进行实例化。类可以有构造函数用于初始化对象。

  2. 接口: 接口用于定义对象的形状,不包含实现,只有属性和方法的声明。接口不能直接被实例化,但可以用于类型检查和约束。

  3. 继承与实现: 类可以继承其他类,而接口可以被类实现。一个类只能继承一个类,但可以实现多个接口。

  4. 抽象性: 类可以包含具体的实现,而接口只能包含抽象的声明。

  5. 相似性: 在某些情况下,类和接口可以达到相似的目的,但它们的用途不同。类用于创建对象的实例,而接口用于定义对象的形状。

总之,类和接口在 TypeScript 中有不同的用途,类用于实现对象的行为和状态,而接口用于定义对象的外部结构。根据具体的需求,你可以选择使用类、接口或它们的组合来构建你的程序。

泛型

泛型(Generics)是一种在编程中用于创建可重用、通用代码的技术。它允许你编写函数、类或接口,可以适用于多种数据类型,而不仅仅限于单一类型。泛型在提高代码的灵活性、可重用性和类型安全性方面发挥着重要作用。

泛型的核心思想是参数化类型,它使得你能够在使用代码时,为特定的数据类型提供具体的类型。

泛型函数示例:

typescript 复制代码
function identity<T>(arg: T): T {
    return arg;
}

let output = identity<string>("Hello, TypeScript!"); // 指定泛型类型为 string
let output2 = identity<number>(42); // 指定泛型类型为 number

// TypeScript 类型推断也可以自动推断泛型类型
let inferredOutput = identity(true); // 推断出泛型类型为 boolean

泛型类示例:

typescript 复制代码
class Box<T> {
    value: T;

    constructor(value: T) {
        this.value = value;
    }
}

let numberBox = new Box<number>(42);
let stringBox = new Box<string>("Hello");

泛型接口示例:

typescript 复制代码
interface KeyValuePair<K, V> {
    key: K;
    value: V;
}

let entry: KeyValuePair<string, number> = { key: "age", value: 25 };

泛型不仅可以用于数据类型,还可以用于函数签名、类方法、类属性等地方。泛型的使用可以提高代码的灵活性和可维护性,同时保持类型安全。

泛型约束

有时候你想要对泛型参数的类型进行约束,确保它满足特定的条件。这时可以使用泛型约束。

typescript 复制代码
interface Lengthwise {
    length: number;
}

function logLength<T extends Lengthwise>(arg: T): void {
    console.log(arg.length);
}

logLength("Hello"); // 输出:5
logLength([1, 2, 3]); // 输出:3
logLength(42); // 错误,因为 number 类型没有 length 属性

在这个例子中,T extends Lengthwise 表示泛型参数 T 必须满足 Lengthwise 接口的约束,即具有 length 属性。

泛型在编写可复用且灵活的代码时非常有用,可以根据不同的数据类型自动适应,提高代码效率和可读性。

枚举

枚举(Enum)是一种用于定义一组命名常量的方式,它可以在代码中更直观地表示一组相关的取值。在 TypeScript 中,枚举提供了一种方便的方式来定义命名的整数常量,从而提高代码的可读性和可维护性。

定义枚举:

typescript 复制代码
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

在这个例子中,Direction 枚举定义了四个常量:UpDownLeftRight。默认情况下,枚举成员的值从 0 开始自增。你也可以显式指定枚举成员的值:

typescript 复制代码
enum Direction {
    Up = 1,
    Down,
    Left,
    Right,
}

在这个例子中,Up 的值为 1,Down 为 2,以此类推。

使用枚举:

typescript 复制代码
let playerDirection = Direction.Right;
console.log(playerDirection); // 输出:3

if (playerDirection === Direction.Right) {
    console.log("Player is moving to the right.");
}

你可以将枚举成员赋值给变量,然后在代码中使用这些变量。在上面的例子中,playerDirection 被赋值为 Direction.Right,然后使用了一个条件语句检查它是否等于 Direction.Right

字符串枚举:

默认情况下,枚举成员的值是数字。但你也可以使用字符串枚举,其中每个成员的值都是字符串。

typescript 复制代码
enum LogLevel {
    Error = "ERROR",
    Warning = "WARNING",
    Info = "INFO",
}

let logLevel = LogLevel.Warning;
console.log(logLevel); // 输出:WARNING

反向映射:

枚举还提供了一种从值到枚举成员的反向映射。

typescript 复制代码
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

let directionName = Direction[2];
console.log(directionName); // 输出:Left

在这个例子中,Direction[2] 返回的是字符串 "Left",这是因为枚举成员 Left 的值为 2。

枚举在代码中可以增加可读性,避免使用魔法数值,使代码更具有可维护性。无论是用于表示状态、选项还是一组相关的值,枚举都能起到很好的作用。

命名空间和模板

当你的代码项目变得越来越复杂,为了避免命名冲突和更好地组织代码,你可以使用 TypeScript 中的命名空间(Namespace)和模块(Module)。

命名空间(Namespace)

命名空间是一种将相关的代码组织在一起的方式,以避免全局命名冲突。它可以将相关的函数、类、接口等封装在一个命名空间内部,然后在其他地方通过命名空间进行访问。

定义命名空间:

typescript 复制代码
namespace MyNamespace {
    export interface Person {
        name: string;
        age: number;
    }

    export function sayHello(person: Person) {
        console.log(`Hello, my name is ${person.name} and I'm ${person.age} years old.`);
    }
}

使用命名空间:

typescript 复制代码
let person: MyNamespace.Person = { name: "Alice", age: 25 };
MyNamespace.sayHello(person);

在这个例子中,我们创建了一个名为 MyNamespace 的命名空间,里面包含了一个 Person 接口和一个 sayHello 函数。通过 export 关键字,我们将这些成员暴露出来,从而能够在外部使用。

模块(Module)

模块是将代码组织为可重用、可维护的单元。与命名空间不同,模块将代码封装起来,并且默认处于严格模式,不会自动暴露成员到全局作用域。

定义模块:

typescript 复制代码
// math.ts
export function add(a: number, b: number): number {
    return a + b;
}

export function subtract(a: number, b: number): number {
    return a - b;
}

使用模块:

typescript 复制代码
// app.ts
import { add, subtract } from './math';

console.log(add(10, 5)); // 输出:15
console.log(subtract(10, 5)); // 输出:5

在这个例子中,我们在 math.ts 文件中定义了两个函数,并使用 export 关键字将它们导出。然后在 app.ts 文件中使用 import 关键字导入并使用这些函数。

模块提供了更强大的封装和代码复用机制,适用于构建复杂的应用程序。与命名空间相比,模块更适合用于分布式开发、跨项目共享和管理依赖。

装饰器

装饰器(Decorators)是 TypeScript 的一项特性,用于在类、方法、属性等声明之前进行修饰和增强。装饰器提供了一种便捷的方式来修改类或其成员的行为,同时也能使代码更加易读和模块化。

装饰器在实际开发中常用于 AOP(面向切面编程),用来添加额外的功能或修改现有的行为,例如日志记录、验证、性能分析等。

使用装饰器

在 TypeScript 中,装饰器由 @ 符号紧跟一个函数名来表示,并放置在要修饰的目标前面。

typescript 复制代码
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function(...args: any[]) {
        console.log(`Calling method ${propertyKey} with arguments ${args}`);
        const result = originalMethod.apply(this, args);
        console.log(`Method ${propertyKey} returned: ${result}`);
        return result;
    };

    return descriptor;
}

class Calculator {
    @log
    add(a: number, b: number): number {
        return a + b;
    }
}

const calculator = new Calculator();
calculator.add(10, 20); // 调用 add 方法,并在控制台中输出日志

在这个例子中,@log 装饰器被应用到 add 方法上。装饰器修改了 add 方法的行为,添加了额外的日志输出。装饰器接收三个参数:目标对象、成员名、成员的属性描述符。通过修改属性描述符的 value,我们可以修改原始方法的实现。

装饰器工厂

装饰器可以有参数,当一个装饰器需要定制化的行为时,可以使用装饰器工厂。

typescript 复制代码
function logWithMessage(message: string) {
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const originalMethod = descriptor.value;

        descriptor.value = function(...args: any[]) {
            console.log(message);
            const result = originalMethod.apply(this, args);
            return result;
        };

        return descriptor;
    };
}

class Calculator {
    @logWithMessage("Calling add method")
    add(a: number, b: number): number {
        return a + b;
    }
}

在这个例子中,@logWithMessage("Calling add method") 是一个装饰器工厂,它返回一个装饰器函数,用于修改 add 方法的行为。

装饰器为 TypeScript 添加了更大的灵活性和可扩展性,使得在代码中添加、修改功能变得更加优雅和模块化。需要注意的是,装饰器在不同的上下文中(例如类、属性、方法等)可能有不同的行为和作用。

相关推荐
CallBack8 个月前
Typora+PicGo+阿里云OSS搭建个人图床,纵享丝滑!
前端·青训营笔记
Taonce1 年前
站在Android开发者的角度认识MQTT - 源码篇
android·青训营笔记
AB_IN1 年前
打开抖音会发生什么 | 青训营
青训营笔记
monster1231 年前
结营感受(go) | 青训营
青训营笔记
翼同学1 年前
实践记录:使用Bcrypt进行密码安全性保护和验证 | 青训营
青训营笔记
hu1hu_1 年前
Git 的正确使用姿势与最佳实践(1) | 青训营
青训营笔记
星曈1 年前
详解前端框架中的设计模式 | 青训营
青训营笔记
tuxiaobei1 年前
文件上传漏洞 Upload-lab 实践(中)| 青训营
青训营笔记
yibao1 年前
高质量编程与性能调优实战 | 青训营
青训营笔记
小金先生SG1 年前
阿里云对象存储OSS使用| 青训营
青训营笔记