在上一节中,我们用一个简单的回声函数(echo)初步体验了泛型的魅力。实际上,像那种在定义时带有 “<T>” 的函数,就是 “泛型函数”。
在真实的项目开发中,泛型函数经常用于处理复杂的数据结构(如数组、对象、元组等)。咱们在这一节中,将带领小伙伴们学习一下泛型函数在真实业务中的玩法。
TypeScript 泛型函数与数组
在实际开发中,我们经常需要编写一些工具函数来处理数组。假设我们需要一个函数:根据传入的长度和初始值,自动生成一个数组。
如果不使用泛型,我们可能会用 any 来兜底,但这会导致生成的数组丢失所有类型提示。此时,泛型函数就派上大用场了。
示例 1:创建一个通用的数组生成函数
// 定义泛型函数,T 代表初始值的类型
function createArray<T>(length: number, value: T): T[] {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result.push(value);
}
return result;
}
// 传入数字
const numArr = createArray(3, 2026);
console.log(numArr);
// 传入字符串
const strArr = createArray(3, "绿叶网");
console.log(strArr);运行结果如下。
[ 2026, 2026, 2026 ]
[ '绿叶网', '绿叶网', '绿叶网' ]分析:
我们在函数名 createArray() 后面加上 “<T>” 来声明了一个泛型变量,然后参数 value 的类型被指定为 “T”。最巧妙的地方是,返回值类型被指定为 “T[]”(T类型的数组)。
当我们传入 "绿叶网" 时,TypeScript 会自动推断 “T” 是 string,进而推断出 strArr 的类型是 string[]。后续如果我们对 strArr 使用数组方法,则编辑器会提供完美的智能提示。
TypeScript 函数的多泛型参数(<T, U>)
有时候,一个函数需要处理多个不同类型的参数。既然泛型是变量,那我们当然可以声明多个泛型变量。
一般情况下,我们会按照字母表的顺序,继续使用 U、V 等字母来代表第 2个、第 3 个泛型变量。
示例 2:多泛型参数实现 “元组交换”
// 声明 2 个泛型变量:T 和 U
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
// 传入 [数字, 字符串]
const swappedResult = swap([2026, "绿叶网"]);
console.log(swappedResult[0]);
console.log(swappedResult[1]);运行结果如下。
绿叶网
2026分析:
在这个例子中,我们定义了一个函数 swap(),用于接收一个包含两个元素的元组,然后把它们的位置调换一下再返回。
当我们给 swap() 函数传入 [2026, "绿叶网"] 作为参数时,TypeScript 会自动推断第 1 个参数类型是 number,第 2 个参数是 string。然后将 “T” 绑定为了 string,将 “U” 绑定为了 number。
由于函数返回值类型写的是 [U, T],因此 TypeScript 也能知道返回的结果必然是 [string, number] 类型。
TypeScript 箭头函数中的泛型
在现代前端开发(如 Vue 3 的 Composition API)中,我们会大量使用箭头函数。在箭头函数中使用泛型,语法稍有不同,我们需要将 “<T>” 放在括号的前面。
示例 3:箭头函数泛型写法
// 箭头函数定义泛型变量
const echo = <T>(arg: T): T => {
return arg;
};
const result = echo("绿叶网");
console.log(result);运行结果如下。
绿叶网分析:
需要注意的是,如果你是在 “Vue 的 .tsx 文件” 或者 “React 的 .jsx 文件” 中编写上面的箭头函数,此时编辑器大概率会亮起红色的波浪线报错。
这是因为在 TSX/JSX 语法中,编译器看到 <T> 时,会误以为这是一个 HTML 标签(类似于 <div>),从而导致解析失败。
为了明确告诉编译器 “这是一个泛型、不是 HTML 标签”,业界有一个非常经典的 Hack 写法——在泛型字母后面加一个英文逗号(,)。
// 强烈推荐:在泛型后加逗号,完美兼容 TSX 语法
const safeEcho = <T,>(arg: T): T => {
return arg;
};