- 作者:老汪软件技巧
- 发表时间:2024-09-25 04:00
- 浏览量:
在 TypeScript 中,避免使用 Function 作为类型。Function 代表的是“任意类型的函数”,这会带来类型安全问题。对于绝大多数情况,你可能更希望明确地指定函数的参数和返回值类型。
如果你确实想表达一个可以接收任意数量参数并返回任意类型的函数,可以使用 (...args: any[]) => any 这种形式。它明确表示函数的参数是一个任意长度的数组,并且返回值的类型也是任意的,这样既保留了灵活性,也提供了足够的类型信息。
使用函数类型的案例
想象一下,你正在编写一个函数,用于对数组中的元素求和。这在 Excalidraw 代码库中有一个很好的例子:
const sum = (
array: readonly T[],
mapper: (item: T) => number
): number =>
array.reduce(
(acc, item) => acc + mapper(item),
0
);
这个 sum 函数的定义很简单,它接收两个参数:
该函数最终返回一个 number,也就是数组中所有项通过 mapper 函数处理后的和。
mapper 函数的作用
mapper 函数是这个模式中的关键部分。让我们单独看看 mapper 的定义:
type Mapper = (item: T) => number;
mapper 的类型声明表示,它接收一个类型为 T 的参数,并返回一个数字。
假设你有一个 YouTube 视频对象的数组,你想统计所有视频的观看次数。这是一个可能的使用案例:
interface YouTubeVideo {
name: string;
views: number;
}
const youTubeVideos: YouTubeVideo[] = [
{ name: "My favorite cheese", views: 100 },
{ name: "My second favorite cheese (you won't believe it)", views: 67 }
];
const mapper: Mapper<YouTubeVideo> = (video) => video.views;
const result = sum(youTubeVideos, mapper); // 167
在这个例子中,mapper 函数从每个 YouTubeVideo 对象中提取 views 属性,并将它们相加,最终得到了 167 这个总数。
类型推断的优势
TypeScript 的类型推断能力非常强大。在上面的例子中,即便你省略了显式的类型声明,TypeScript 依然可以根据上下文推断出正确的类型:
const youTubeVideos = [
{ name: "My favorite cheese", views: 100 },
{ name: "My second favorite cheese (you won't believe it)", views: 67 }
];
const result = sum(youTubeVideos, (video) => video.views); // 167
虽然没有显式声明 youTubeVideos 的类型,但 TypeScript 仍然能够推断出 video 的类型是 { name: string; views: number }。这是因为我们在 sum 函数的定义中明确了 mapper 的函数签名 (item: T) => number,因此 TypeScript 可以自动推导出类型。
避免使用 Function 类型
在开发中,很多初学者会犯一个常见的错误:将函数类型声明为 Function。比如说,以下这种写法就存在问题:
const sum = (
array: readonly T[],
mapper: Function
): number =>
array.reduce(
(acc, item) => acc + mapper(item),
0
);
使用 Function 作为类型实际上是在告诉 TypeScript:mapper 可以是任何函数,这样的声明没有提供足够的类型约束,导致类型检查变得不那么严格。一个潜在的问题是,开发者可能会无意中传入不返回数字的函数:
const result = sum(youTubeVideos, (item) => {
return item.name;
});
在这个例子中,mapper 返回的是 name(字符串)而非 views(数字),但由于 Function 类型没有约束返回值类型,TypeScript 无法捕捉到这个错误。
更好的函数类型表达
如果你想要表达“任何函数”,可以使用更具体的函数签名,而非 Function。例如,如果你想表示接收任意参数且返回任意值的函数,可以使用 (...args: any[]) => any 这样的形式。这个签名意味着函数可以接收任意数量的参数,并且可以返回任意类型的值。
TypeScript 还提供了一些内置的实用类型,如 Parameters 和 ReturnType,可以帮助你推断函数的参数和返回值类型:
export type Parameters<
T extends (...args: any) => any
> = T extends (...args: infer P) => any
? P
: never;
export type ReturnType<
T extends (...args: any) => any
> = T extends (...args: any) => infer R ? R : any;
Parameters 可以获取函数 T 的参数类型,而 ReturnType 则可以获取其返回值类型。这些实用类型都使用了 (...args: any) => any 作为约束,表示可以接收任意参数并返回任意类型的函数。
总结
在 TypeScript 中,尽量避免使用 Function 作为类型。它过于宽泛,会导致类型检查失效。通过使用 (a: string) => any 或 (...args: any[]) => any 这样的具体类型签名,你可以在获得类型安全的同时保留代码的灵活性。
这不仅能帮助你捕捉潜在的类型错误,还能让你的代码在团队协作和项目维护中更加健壮。