TypeScript中的 K、T 、V

封面

前言

TypeScript 的泛型里经常会碰到一些字母,比如 KTV,是不是觉得很奇怪?

泛型类型

泛型参数
图中的 T 称为泛型类型参数,它是我们希望传递给函数的类型占位符

使用泛型类型参数可以使函数、类或接口在处理不同类型的数据时变得更加灵活通用。当我们定义泛型类型参数时,它会在函数、类或接口中作为一个占位符,表示可以接受任意类型的值。

链接关系

就像传递参数一样,我们获取用户指定的实际类型并将其链接参数类型返回值类型

链接到参数类型和返回值类型
链接到参数类型和返回值类型

K、T、V 含义

那么是什么T意思呢?图中的泛型类型参数T代表Type,实际上T可以替换为任何有效的名称。除了 之外T,常见的泛型变量还有 KVE 等。

  • K(Key):表示对象中key的类型
  • V(Value):表示对象中值的类型
  • E(Element):表示元素类型

K、T、V

K 和 V:

// 定义一个泛型接口,表示键值对
interface KeyValuePair<K, V> {
    key: K;
    value: V;
}

// 使用 K 和 V 分别表示键和值的类型
let pair1: KeyValuePair<string, number> = { key: "age", value: 25 };
let pair2: KeyValuePair<number, boolean> = { key: 1, value: true };

E实现一个简单的队列(Queue)数据结构:

// 定义一个泛型队列类
class Queue<E> {
    private elements: E[] = [];

    // 将元素添加到队列末尾
    enqueue(element: E) {
        this.elements.push(element);
    }

    // 从队列头部移除并返回元素
    dequeue(): E | undefined {
        return this.elements.shift();
    }

    // 返回队列中的所有元素
    getAll(): E[] {
        return this.elements;
    }
}

// 使用泛型队列存储数字
const numberQueue = new Queue<number>();
numberQueue.enqueue(1);
numberQueue.enqueue(2);
numberQueue.enqueue(3);
console.log(numberQueue.getAll()); // Output: [1, 2, 3]
console.log(numberQueue.dequeue()); // Output: 1

// 使用泛型队列存储字符串
const stringQueue = new Queue<string>();
stringQueue.enqueue("apple");
stringQueue.enqueue("banana");
console.log(stringQueue.getAll()); // Output: ['apple', 'banana']
console.log(stringQueue.dequeue()); // Output: "apple"

当然,不必只定义一个类型参数,可以引入任意数量类型参数。这里我们引入了一个新的类型参数U,它扩展了我们定义的函数。

定义U
自动类型推断

自动类型推断

在调用identity函数时,我们可以显式指定泛型参数的实际类型
当然,你也可以不指定泛型参数的类型,让TypeScript自动帮我们完成类型推断

自动推断类型
自动推断类型

看完上面的动画,你是否已经了解泛型类型参数了?

泛型的应用场景

泛型在许多场景下都非常有用。下面是一些常见的泛型应用场景:

容器类和数据结构

泛型可以用于创建容器类数据结构,如数组、链表、栈和队列等。通过使用泛型,我们可以在这些数据结构中存储和操作不同类型的数据,而不需要为每种类型都编写单独的实现。


// 使用泛型创建一个通用的数组栈
class Stack<T> {
    private items: T[] = [];

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

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

// 使用字符串类型的栈
const stringStack = new Stack<string>();
stringStack.push("Hello");
stringStack.push("World");
console.log(stringStack.pop()); // Output: "World"

// 使用数字类型的栈
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // Output: 2

函数和方法

泛型函数和方法可以适用于多种类型参数返回值。这使得我们可以编写更加通用和灵活的函数,而不需要为每种类型都编写重复的代码。


// 泛型函数用于反转数组
function reverseArray<T>(array: T[]): T[] {
    return array.reverse();
}

const numbers = [1, 2, 3, 4, 5];
const reversedNumbers = reverseArray(numbers); // Output: [5, 4, 3, 2, 1]

const strings = ["apple", "banana", "orange"];
const reversedStrings = reverseArray(strings); // Output: ["orange", "banana", "apple"]

接口和类

泛型还可以用于定义接口和类。通过使用泛型,我们可以创建可重用的接口和类,以适应不同类型的数据。这在编写通用的数据结构、算法和组件时非常有用。

使用泛型类型参数的接口:


// 泛型接口定义一个通用的 Pair 类型
interface Pair<T, U> {
    first: T;
    second: U;
}

// 使用字符串和数字的 Pair
const pair1: Pair<string, number> = { first: "one", second: 1 };
const pair2: Pair<number, boolean> = { first: 42, second: true };

使用泛型类型参数的类:

class Box<T> {
  private value: T;

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

  getValue(): T {
    return this.value;
  }

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

let box = new Box<string>("Hello");
console.log(box.getValue());  // output: "Hello"

box.setValue("World");
console.log(box.getValue());  // output: "World"

类型约束和扩展

泛型还可以与类型约束和扩展一起使用,以限制泛型参数的类型范围或添加特定的行为。通过使用类型约束,我们可以确保泛型参数具有特定的属性或方法。这提供了更强的类型安全性和代码可读性。


// 使用类型约束确保泛型参数具有 length 属性
function getLength<T extends { length: number }>(obj: T): number {
    return obj.length;
}

console.log(getLength("Hello")); // Output: 5
console.log(getLength([1, 2, 3])); // Output: 3
console.log(getLength({ length: 10, width: 5 })); // Output: 10

// 使用类型约束确保泛型参数为数字类型
function sum<T extends number>(a: T, b: T): T {
    return a + b;
}

console.log(sum(3, 4)); // Output: 7
console.log(sum(2, "test")); // Error: Argument of type '"test"' is not assignable to parameter of type 'number'.

常用的工具类型

为了方便开发者 TypeScript 内置了一些常用的工具类型:

Partial:将类型 T 中的所有属性变为可选属性。

interface User {
    name: string;
    age: number;
    email: string;
}

function updateUser(user: Partial<User>, newName: string): User {
    return { ...user, name: newName } as User; 
}

const user: User = {
    name: "Alice",
    age: 30,
    email: "alice@example.com",
};

const updatedUser = updateUser({ age: 35 }, "Bob");
console.log(updatedUser);

Required:将类型 T 中的所有属性变为必选属性。

interface Props {
    name?: string;
    age?: number;
}

function printInfo(info: Required<Props>) {
    console.log(info.name.toUpperCase(), info.age.toFixed(2));
}

printInfo({ name: "John", age: 30 });

Readonly:将类型 T 中的所有属性变为只读属性。

interface Point {
    x: number;
    y: number;
}

const point: Readonly<Point> = { x: 10, y: 20 };
// point.x = 5; // Error: Cannot assign to 'x' because it is a read-only property

Record<K, T>:创建一个具有指定键类型 K 和值类型 T 的对象类型

type Fruit = "apple" | "banana" | "orange";
const prices: Record<Fruit, number> = {
    apple: 1.5,
    banana: 2,
    orange: 1,
};

Pick<T, K>:从类型 T 中选择部分属性 K 组成新类型。

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

type PersonInfo = Pick<Person, "name" | "age">;
// PersonInfo: { name: string; age: number; }

keyof: 获取给定类型的所有键(属性名)的联合类型。

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

type PersonKeys = keyof Person;

// PersonKeys 类型为 "name" | "age" | "email"

  • 50
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@才华有限公司

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值