泛型
什么是泛型
范型是编程语言中的一种特性,它允许我们定义函数、类或接口时使用类型参数,这些类型参数可以灵活地适应不同的数据类型。范型使得代码更加通用和灵活,能够处理多种数据类型,同时保持类型安全。
泛型语法
function identity<T>(arg: T): T {
return arg;
}
多个类型参数:
function identity<T, U>(arg1: number, arg2: T, arg3: U): [number, T, U] {
return [arg1, arg2, arg3];
}
使用示例:
let result = identity<string, number>(1, "hello", 2);
// result 的类型是 [number, string, number]
|| 或者
let result2 = identity(1, "hello", 2);
// result2 的类型是 [number, string, number]
<T, U>
是类型参数声明,可以理解为定义了两个类型变量。类型参数可以有多个,多个类型参数之间用逗号 ,
隔开。
在 TypeScript 中,<T, U>
是类型参数,可以理解为占位符,用于在函数中表示类型。它们并不直接对应函数参数的位置,而是用于定义函数参数和返回值的类型关系。
类型参数的声明顺序(如 <T, U>
)与函数参数的位置无关。类型参数是用来灵活定义类型的工具,可以在函数签名的任何位置使用。
比如在上面的例子中:
虽然
T
在类型参数中排在第一位,但它被用于第二个参数arg2
U
在类型参数中排在第二位,但它被用于第一个和第三个参数arg1
和arg3
泛型实际应用
分布式条件类型
分布式条件类型是指在条件类型中,当类型参数是一个联合类型时,条件类型会被应用到联合类型的每个元素上。
语法:
T extends U ? X : Y
使用示例:
type IsArray<T> = T extends Array<any> ? true : false;
const isArrayCheck1: IsArray<number[]> = true; // 正确,类型为 true
const isArrayCheck2: IsArray<string> = false; // 正确,类型为 false
// 实际应用:根据传入的参数类型不同执行不同的处理
function handleInput<T>(input: T): void {
if (Array.isArray(input)) {
console.log("处理数组:", input);
} else {
console.log("处理单个值:", input);
}
}
// 使用示例
handleInput([1, 2, 3]); // 输出: 处理数组: [1, 2, 3]
handleInput("Hello"); // 输出: 处理单个值: Hello
自定义 Partial 类型
// 自定义 Partial 类型,使所有属性变为可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface User {
name: string;
age: number;
email: string;
}
// 使用 Partial 进行可选属性的更新
function updateUser(id: number, updates: Partial<User>): void {
// 假设在这里我们更新用户
console.log(`更新用户 ID ${id} 的信息:`, updates);
}
// 实际应用:更新用户信息,只传入需要更新的属性
updateUser(1, { age: 30 }); // 只更新年龄
updateUser(2, { name: "Bob", email: "bob@example.com" }); // 更新姓名和邮箱
碰到的问题
写一个函数,传入的参数不能是 Ref 类型 ,但是传入的参数要是个对象。
import { ref, Ref, unref } from "vue";
type NotRef<T> = T extends Ref<any> ? never : T;
const ces = <T>(value: NotRef<T>) => {};
const a = ref<{ name: string }>({ name: "123" });
ces(unref(a)); // 正确
ces(a.value); // 正确
ces(a); // 类型“Ref<{ name: string; }>”的参数不能赋给类型“never”的参数。
ces([a, "123"]); // 正确
这里为什么 ces(a)
会报错,而 ces([a, "123"])
不报错?
让我们逐步分析:
ces(a)
的情况:- 传入的
a
是Ref<{ name: string }>
类型 - 经过
NotRef<T>
判断,因为它确实是 Ref 类型,所以返回never
- 因此报错
- 传入的
ces([a, "123"])
的情况:- 传入的是一个数组
[Ref<{ name: string }>, string]
- 这个数组整体作为一个类型被传入
NotRef<T>
- 数组类型本身不是 Ref 类型,所以
T extends Ref<any>
判断为false
- 因此返回原类型,可以正常使用
- 传入的是一个数组
但是 const ces = <T>(value: NotRef<T>) => {};
传入的参数是 [Ref<{ name: string }>, string]
类型,这里就要用到泛型约束。
泛型约束
泛型约束是指在定义泛型时,对类型参数进行约束,以确保传入的参数满足特定条件。
语法:
<T extends U>
使用示例:
import { ref, Ref, unref } from "vue";
type NotRef<T> = T extends Ref<any> ? never : T;
const ces = <T extends object>(value: T) => {};
const a = ref<{ name: string }>({ name: "123" });
ces(unref(a)); // 正确
ces(a.value); // 正确
ces(a); // 类型“Ref<{ name: string; }>”的参数不能赋给类型“never”的参数。
ces([a, "123"]); // 正确,🌟🌟🌟因为对象类型包括普通对象、数组、函数等,因为在 TypeScript 中,数组和函数都被视为对象的一种。
ces(() => {}); // 正确
ces(123); // 错误,因为 123 是 number 类型,不是对象类型