在TypeScript中,any
类型是一个非常强大的类型。它允许你将一个值当作在JavaScript中一样来处理,而不是在TypeScript中。这意味着它禁用了TypeScript的所有特性,包括类型检查、自动补全和安全性。
const myFunction = (input: any) => {
input.someMethod();
};
myFunction("abc"); // This will fail at runtime!
在实际项目中,any 可能会造成以下几个问题:
- 丧失类型安全性:使用 any 会导致 TypeScript 不再检查你的代码是否存在潜在的错误,这与引入 TypeScript 的初衷背道而驰。
- 难以维护:当代码库中充斥着大量的 any 类型时,调试和维护变得更加困难。你无法通过类型系统获得代码的准确信息。
- 隐藏错误:因为 any 允许你调用不存在的属性或方法,这可能会导致隐藏的 bug 只有在运行时才暴露出来,而不是在编译时发现。
在大多数情况下,any
类型被认为是bad code。并会使用eslint来避免使用any
。然而,有一些情况使用 any
却是正确的选择。
类型参数约束
假设我们要在TypeScript中实现一个ReturnType
的工具方法,该方法接受一个函数类型,并返回它的返回值类型。
我们需要创建一个泛型类型,这个泛型类型接受一个函数类型作为其类型参数。如果我们限制自己不使用任何特定的类型(例如any
),我们可能会选择使用unknown
类型。unknown
是比 any
更加严格和安全的类型。它允许你存储任何类型的值,但在你使用该值之前必须进行类型检查,不能随意调用属性或方法。
type ReturnType<T extends (...args: unknown[]) => unknown> = T extends (...args: unknown[]) => infer R ? R : never;
- ReturnType<T>: 这里定义了一个泛型类型ReturnType,它接受一个类型参数T。
类型约束: T extends (...args: unknown[]) => unknown
: 这个约束指定T必须是一个函数类型,这个函数接受一个参数列表(每个参数都是unknown类型),并返回一个unknown类型的值。
条件类型:T extends (...args: unknown[]) => infer R ? R : never
: 这是一个条件类型表达式,它使用了infer关键字来推断函数返回的具体类型。infer R
: 这部分尝试从满足(...args: unknown[]) => unknown类型的函数中推断出返回类型R。
? R: 如果推断成功,那么ReturnType<T>的结果是推断出的返回类型R。: never
: 如果推断失败(例如,如果T不是一个函数类型或者函数的返回类型不是unknown),则ReturnType<T>的结果是never类型,表示这个类型不存在任何值。
但一旦我们添加一个参数,它就失效了。提示无法把unknown赋值给string。
实际上,如果我们参数是unknown的话,它确实是生效的。
只接受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>;
现在它按预期工作了。我们不在乎函数接受什么类型——它可以是任意类型。因为我们本来的目的就是创建一个不严格的类型,所以这是安全的。
从通用函数中返回条件类型
在某些情况下,TypeScript的类型缩小能力可能不足以满足需求。例如,你可能希望创建一个函数,根据条件返回不同的类型。
const youSayGoodbyeISayHello = (
input: "hello" | "goodbye"
) => {
if (input === "goodbye") {
return "hello";
} else {
return "goodbye";
}
};
const result = youSayGoodbyeISayHello("hello");
在这里,result
会被推断成"hello" | "goodbye"
。但我们传进去的是hello
嗄,hello
返回的应该就是goodbye
。这显然是不准确的。我们可以这样子改造一下:
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";
}
};
我们为函数的返回类型添加了一个条件类型,它反映了我们的运行时逻辑。通过对结果做类型转换,实现我们想要的结果。但这样子做会有一个弊端,每个类型都要写一次。那有没有更好的方式呢?使用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;
}
};
如果不使用 any
,TypeScript可能无法正确地将条件类型与运行时逻辑相匹配,导致类型错误。
结论
一个问题仍然存在:你应该禁止any
吗?答案应该是肯定的。您应该打开ESLint
规则,以防止其使用,并且您应该尽可能避免使用它。
然而,有些情况下需要any
。他们值得使用eslint-disable
来绕过它们。
本文由mdnice多平台发布
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。