• 作者:老汪软件技巧
  • 发表时间:2024-09-17 17:03
  • 浏览量:

在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 extends (...args: unknown[]) => unknown> = T extends (...args: unknown[]) => infer R ? R : never;

但一旦我们添加一个参数,它就失效了。提示无法把unknown赋值给string。

实际上,如果我们参数是unknown的话,它确实是生效的。

只接受unknown作为参数的效果显然不是我们想要的。我们希望它对任何函数都有效。这时候就不得不使用any[]作为类型参数约束:

type ReturnType extends (...args: any[]) => any> =
  T extends (...args: any[]) => infer R ? R : never;
const myFunction = (input: string) => {
  console.log("Hey!");
};
type Result = ReturnType;

现在它按预期工作了。我们不在乎函数接受什么类型——它可以是任意类型。因为我们本来的目的就是创建一个不严格的类型,所以这是安全的。

从通用函数中返回条件类型

在某些情况下,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来绕过它们。