第 23 期 - The Use and Risks of `any` in TypeScript
摘要
文章讲述 TypeScript 中any
类型虽通常有害,但在如实现 ReturnType 工具类型、处理函数返回类型逻辑等特定高级用例中有必要性,还探讨是否应在代码库中禁用any
类型。
一、any
类型在 TypeScript 中的一般性危害
any
在 TypeScript 里是非常强大的类型。它让你能像在 JavaScript 中而非 TypeScript 里对待一个值。这意味着它会禁用 TypeScript 的所有特性,比如类型检查、自动补全和安全性。例如以下代码:
const myFunction = (input: any) => {
input.someMethod();
};
myFunction("abc"); // 这在运行时会失败!
多数社区成员认为使用any
是有害的,有 ESLint 规则来防止它的使用,这可能会让开发者完全不想使用any
。
二、any
类型在特定高级用例中的必要性
(一)实现 ReturnType 工具类型
- 首先尝试不用
any
,用unknown
来创建一个泛型类型来实现 ReturnType(接受一个函数类型作为类型参数,返回函数返回值的类型):
type ReturnType<T extends (...args: unknown[]) => unknown> =
// 这里对我们的解释不重要:
T extends (...args: unknown[]) => infer R? R : never;
这里约束T
扩展(...args: unknown[]) => unknown
,意思是只有接受unknown[]
参数数组并且返回unknown
的函数才被允许。
2. 当函数没有参数时似乎能正常工作:
const myFunction = () => {
console.log("Hey!");
};
type Result = ReturnType<typeof myFunction>;type Result = void
- 但一旦添加参数就不行了:
const myFunction = (input: string) => {
console.log("Hey!");
};
type Result = ReturnType<typeof myFunction>;
// 会出现类型不满足约束的错误
- 即使将函数参数改为
input: unknown
,也不是我们想要的效果,因为这样创建的 ReturnType 函数只对接受unknown
作为参数的函数有效。 - 正确的解决方案是使用
any[]
作为类型参数约束:
type ReturnType<T extends (...args: any[]) => any> =
T extends (...args: any[]) => infer R? R : never;
const myFunction = (input: string) => {
console.log("Hey!");
};
type Result = ReturnType<typeof myFunction>;type Result = void
这里声明不在乎函数接受什么类型,这是any
的一种安全使用。
(二)处理函数返回类型逻辑
- 例如创建一个根据条件返回不同类型的函数:
const youSayGoodbyeISayHello = (
input: "hello" | "goodbye"
) => {
if (input === "goodbye") {
return "hello";
} else {
return "goodbye";
}
};
const result = youSayGoodbyeISayHello("hello");const result: "hello" | "goodbye"
这个函数没有达到我们想要的效果,我们希望传入hello
时返回goodbye
,但目前result
的类型是hello
| goodbye
。
2. 通过添加条件类型来修复:
const youSayGoodbyeISayHello = <
TInput extends "hello" | "goodbye"
>(
input: TInput
): TInput extends "hello"? "goodbye" : "hello" => {
if (input === "goodbye") {
return "hello";
} else {
return "goodbye";
}
};
const goodbye = youSayGoodbyeISayHello("hello");const goodbye: "goodbye"
const hello = youSayGoodbyeISayHello("goodbye");const hello: "hello"
- 但当启用错误提示时会发现问题,TypeScript 似乎没有将条件类型与运行时逻辑匹配起来,
hello
或goodbye
无法从函数中返回。 - 可以使用
as
来修复:
const youSayGoodbyeISayHello = <
TInput extends "hello" | "goodbye"
>(
input: TInput
): TInput extends "hello"? "goodbye" : "hello" => {
if (input === "goodbye") {
return "hello" as TInput extends "hello"
? "goodbye"
: "hello";
} else {
return "goodbye" as TInput extends "hello"
? "goodbye"
: "hello";
}
};
还可以提取逻辑到通用类型。但在这种情况下,使用as any
更合理:
const youSayGoodbyeISayHello = <
TInput extends "hello" | "goodbye"
>(
input: TInput
): TInput extends "hello"? "goodbye" : "hello" => {
if (input === "goodbye") {
return "hello" as any;
} else {
return "goodbye" as any;
}
};
虽然这样会让函数类型安全性降低,但在 TypeScript 检查能力有限的情况下,这样做并添加单元测试是比较接近类型安全的做法。
三、是否应在代码库中禁用any
类型
一个问题是是否应该在代码库中禁止any
。总体来说,答案应该是肯定的。应该开启 ESLint 规则防止使用它,并且尽可能避免使用。但在需要的情况下,可以使用eslint - disable
来绕过规则使用any
。
扩展阅读
Made by 捣鼓键盘的小麦 / © 2025 Front Talk 版权所有