Typescript学习教程,从入门到精通,TypeScript 泛型与类型操作详解(一)(16)

TypeScript 泛型与类型操作详解(一)

TypeScript 提供了强大的类型系统,其中泛型(Generics)和类型操作(Type Manipulation)是其核心特性之一。本文将详细介绍 TypeScript 中的泛型及其相关概念,并通过案例代码进行说明。


一、泛型简介

泛型允许在定义函数、接口或类时,不预先指定具体的类型,而是在使用时指定类型。这种方式提高了代码的复用性和灵活性。

为什么使用泛型

  1. 提高代码重用性
  2. 提供更好的类型安全性
  3. 减少使用 any 类型的需要
  4. 在编译时捕获类型错误

1.1 形式类型参数与实际类型参数

  • 形式类型参数(Type Parameters):在泛型定义中声明的类型占位符,通常使用 等形式。
  • 实际类型参数(Type Arguments):在使用泛型时,传入的具体类型。

示例:

// 定义一个泛型函数,形式类型参数为 T
function identity<T>(arg: T): T {
    return arg;
}

// 使用泛型函数,实际类型参数为 number
let output = identity<number>(42); // output 的类型为 number

// 也可以让 TypeScript 推断类型
let output2 = identity("Hello, TypeScript!"); // output2 的类型为 string

1.2 泛型约束

有时需要对泛型参数进行约束,限制其必须符合某些条件。可以使用 extends 关键字实现。

示例:

// 定义一个接口,描述必须具有 length 属性的类型
interface Lengthwise {
    length: number;
}

// 使用泛型约束,限制 T 必须符合 Lengthwise 接口
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length); // 现在可以安全地使用 length 属性
    return arg;
}

// 正确使用
loggingIdentity("Hello"); // string 符合 Lengthwise 接口

// 错误使用,会在编译时报错
// loggingIdentity(42); // number 不符合 Lengthwise 接口

二、泛型函数

泛型函数允许函数在调用时指定类型参数,从而实现更灵活的参数和返回值类型。

示例:

// 泛型函数,返回第一个元素
function getFirstElement<T>(arr: T[]): T {
    return arr[0];
}

let firstNumber = getFirstElement<number>([1, 2, 3]); // firstNumber 的类型为 number
let firstString = getFirstElement<string>(["a", "b", "c"]); // firstString 的类型为 string

三、泛型接口

接口也可以是泛型的,用于定义泛型函数的类型或泛型对象的结构。

示例:

// 定义一个泛型接口,描述一个包含键值对的对象
interface Pair<K, V> {
    key: K;
    value: V;
}

// 使用泛型接口
let user: Pair<string, number> = {
    key: "age",
    value: 30
};

四、泛型类型别名

类型别名可以使用泛型来创建可复用的复杂类型。

示例:

// 定义一个泛型类型别名,描述一个数组或对象
type Container<T> = T[] | { value: T };

// 使用泛型类型别名
let numberContainer: Container<number> = [1, 2, 3];
let stringContainer: Container<string> = { value: "Hello" };

五、泛型类

类也可以是泛型的,用于创建可复用的组件。

示例:

// 定义一个泛型类,表示一个栈
class Stack<T> {
    private elements: T[] = [];

    push(element: T) {
        this.elements.push(element);
    }

    pop(): T | undefined {
        return this.elements.pop();
    }

    peek(): T | undefined {
        return this.elements[this.elements.length - 1];
    }
}

// 使用泛型类
let numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.peek()); // 输出: 2

let stringStack = new Stack<string>();
stringStack.push("a");
stringStack.push("b");
console.log(stringStack.peek()); // 输出: b

六、局部类型

局部类型是指在函数或块作用域内定义的类型。

示例:

function process<T>(value: T) {
    // 定义一个局部类型
    type Wrapped = { wrapped: T };

    let wrappedValue: Wrapped = { wrapped: value };
    console.log(wrappedValue);
}

process("Hello"); // 输出: { wrapped: "Hello" }

七、联合类型

联合类型表示一个值可以是几种类型之一,使用 | 分隔。

示例:

function printId(id: number | string) {
    console.log("ID:", id);
}

printId(101); // 输出: ID: 101
printId("202"); // 输出: ID: 202

7.1 联合类型字面量

联合类型字面量是指字面量值的联合。

示例:

type Direction = "left" | "right" | "up" | "down";

function move(direction: Direction) {
    console.log("Moving", direction);
}

move("left"); // 输出: Moving left
// move("forward"); // 编译错误

7.2 联合类型的类型成员

联合类型的每个成员类型必须符合所有成员类型的共有属性。

示例:

interface Bird {
    fly(): void;
    layEggs(): void;
}

interface Fish {
    swim(): void;
    layEggs(): void;
}

function getSmallPet(): Bird | Fish {
    // ...
}

let pet = getSmallPet();
pet.layEggs(); // 正确
// pet.fly(); // 错误: Fish 类型没有 fly 方法

八、交叉类型

交叉类型表示一个值同时符合几种类型,使用 & 分隔。

示例:

interface Person {
    name: string;
}

interface Employee {
    employeeId: number;
}

type PersonEmployee = Person & Employee;

let personEmployee: PersonEmployee = {
    name: "Alice",
    employeeId: 123
};

8.1 交叉类型字面量

交叉类型字面量是指字面量值的交叉。

示例:

type Color = "red" & "blue"; // 实际上是一个空类型,因为没有值同时是 "red" 和 "blue"

let color: Color;
// 无法实例化,因为没有值符合 Color 类型

8.2 交叉类型的类型成员

交叉类型的成员类型必须符合所有成员类型的属性。

示例:

interface A {
    a: string;
}

interface B {
    b: number;
}

type C = A & B;

let c: C = {
    a: "Hello",
    b: 42
};

九、交叉类型与联合类型

交叉类型和联合类型可以组合使用,以创建更复杂的类型。

示例:

interface Dog {
    bark(): void;
}

interface Cat {
    meow(): void;
}

type DogCat = Dog & Cat;

function getDogCat(): DogCat {
    return {
        bark() {
            console.log("Woof!");
        },
        meow() {
            console.log("Meow!");
        }
    };
}

let pet: DogCat = getDogCat();
pet.bark(); // 输出: Woof!
pet.meow(); // 输出: Meow!

十、索引类型

索引类型允许在泛型中使用动态属性访问。

10.1 索引类型查询

使用 keyof 操作符获取一个类型的键的联合类型。

示例:

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

type PersonKeys = keyof Person; // "name" | "age" | "address"

10.2 索引访问类型

使用索引访问类型获取某个属性的类型。

示例:

type NameType = Person["name"]; // string
type AgeType = Person["age"]; // number

10.3 索引类型的应用

结合泛型和索引类型,实现更灵活的类型操作。

示例:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

let person: Person = {
    name: "Bob",
    age: 25,
    address: "123 Main St"
};

let name = getProperty(person, "name"); // name 的类型为 string
let age = getProperty(person, "age"); // age 的类型为 number

十一、映射对象类型

映射对象类型允许动态地创建对象类型,基于已有的类型进行转换。

11.1 映射对象类型声明

使用 in 关键字和 keyof 操作符来声明映射对象类型。

示例:

type ReadonlyPerson = {
    readonly [K in keyof Person]: Person[K]
};

let readonlyPerson: ReadonlyPerson = {
    name: "Charlie",
    age: 30,
    address: "456 Elm St"
};

// readonlyPerson.age = 31; // 错误: 属性 age 是只读的

11.2 映射对象类型解析

映射对象类型通过遍历键并应用转换函数来生成新的类型。

示例:

type Stringify<T> = {
    [K in keyof T]: string;
};

type StringifiedPerson = Stringify<Person>;

let stringifiedPerson: StringifiedPerson = {
    name: "Dave",
    age: "25",
    address: "789 Oak St"
};

11.3 映射对象类型的应用

结合泛型和映射对象类型,实现更复杂的类型操作。

示例:

// 定义一个泛型函数,将对象的属性值转换为字符串
function stringifyValues<T>(obj: T): { [K in keyof T]: string } {
    let result = {} as { [K in keyof T]: string };
    for (let key in obj) {
        result[key] = String(obj[key]);
    }
    return result;
}

let person: Person = {
    name: "Eve",
    age: 22,
    address: "321 Pine St"
};

let stringifiedPerson = stringifyValues(person);
// stringifiedPerson 的类型为 { name: string; age: string; address: string }

十二、同态映射对象类型

同态映射对象类型是指在映射过程中保持原有类型的结构。

示例:

// 定义一个泛型函数,创建一个只读版本的映射对象类型
function makeReadonly<T>(obj: T): { readonly [K in keyof T]: T[K] } {
    return Object.freeze(obj);
}

let person: Person = {
    name: "Frank",
    age: 28,
    address: "654 Cedar St"
};

let readonlyPerson = makeReadonly(person);
// readonlyPerson 的属性是只读的
// readonlyPerson.age = 29; // 错误: 属性 age 是只读的

总结

TypeScript 的泛型与类型操作提供了强大的工具,使得开发者能够编写灵活、可复用的代码。通过理解和掌握这些概念,可以显著提升代码质量和开发效率。

案例代码汇总

以下是上述各个部分的完整代码示例:

// 泛型函数
function identity<T>(arg: T): T {
    return arg;
}

let output = identity<number>(42);
let output2 = identity("Hello, TypeScript!");

// 泛型约束
interface Lengthwise {
    length: number;
}

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

// 泛型接口
interface Pair<K, V> {
    key: K;
    value: V;
}

let user: Pair<string, number> = {
    key: "age",
    value: 30
};

// 泛型类型别名
type Container<T> = T[] | { value: T };

let numberContainer: Container<number> = [1, 2, 3];
let stringContainer: Container<string> = { value: "Hello" };

// 泛型类
class Stack<T> {
    private elements: T[] = [];

    push(element: T) {
        this.elements.push(element);
    }

    pop(): T | undefined {
        return this.elements.pop();
    }

    peek(): T | undefined {
        return this.elements[this.elements.length - 1];
    }
}

let numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.peek());

let stringStack = new Stack<string>();
stringStack.push("a");
stringStack.push("b");
console.log(stringStack.peek());

// 局部类型
function process<T>(value: T) {
    type Wrapped = { wrapped: T };

    let wrappedValue: Wrapped = { wrapped: value };
    console.log(wrappedValue);
}

process("Hello");

// 联合类型
function printId(id: number | string) {
    console.log("ID:", id);
}

printId(101);
printId("202");

// 联合类型字面量
type Direction = "left" | "right" | "up" | "down";

function move(direction: Direction) {
    console.log("Moving", direction);
}

move("left");

// 交叉类型
interface Person {
    name: string;
}

interface Employee {
    employeeId: number;
}

type PersonEmployee = Person & Employee;

let personEmployee: PersonEmployee = {
    name: "Alice",
    employeeId: 123
};

// 索引类型
interface Person {
    name: string;
    age: number;
    address: string;
}

type PersonKeys = keyof Person;

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

let person: Person = {
    name: "Bob",
    age: 25,
    address: "123 Main St"
};

let name = getProperty(person, "name");
let age = getProperty(person, "age");

// 映射对象类型
type ReadonlyPerson = {
    readonly [K in keyof Person]: Person[K]
};

let readonlyPerson: ReadonlyPerson = {
    name: "Charlie",
    age: 30,
    address: "456 Elm St"
};

// 映射对象类型解析
type Stringify<T> = {
    [K in keyof T]: string;
};

type StringifiedPerson = Stringify<Person>;

let stringifiedPerson: StringifiedPerson = {
    name: "Dave",
    age: "25",
    address: "789 Oak St"
};

// 映射对象类型的应用
function stringifyValues<T>(obj: T): { [K in keyof T]: string } {
    let result = {} as { [K in keyof T]: string };
    for (let key in obj) {
        result[key] = String(obj[key]);
    }
    return result;
}

let person: Person = {
    name: "Eve",
    age: 22,
    address: "321 Pine St"
};

let stringifiedPerson2 = stringifyValues(person);

// 同态映射对象类型
function makeReadonly<T>(obj: T): { readonly [K in keyof T]: T[K] } {
    return Object.freeze(obj);
}

let person2: Person = {
    name: "Frank",
    age: 28,
    address: "654 Cedar St"
};

let readonlyPerson2 = makeReadonly(person2);

你可能感兴趣的:(网页开发,前端开发,typescript,typescript,学习,javascript,jquery,前端,前端开发,知识点)