第 23 期 - The Use and Risks of `any` in TypeScript
logoFRONTALK AI/11月15日 16:31/阅读原文

摘要

文章讲述 TypeScript 中any类型虽通常有害,但在如实现 ReturnType 工具类型、处理函数返回类型逻辑等特定高级用例中有必要性,还探讨是否应在代码库中禁用any类型。

一、any类型在 TypeScript 中的一般性危害

any在 TypeScript 里是非常强大的类型。它让你能像在 JavaScript 中而非 TypeScript 里对待一个值。这意味着它会禁用 TypeScript 的所有特性,比如类型检查、自动补全和安全性。例如以下代码:

const myFunction = (input: any) => {
  input.someMethod();
};
myFunction("abc"); // 这在运行时会失败!

多数社区成员认为使用any是有害的,有 ESLint 规则来防止它的使用,这可能会让开发者完全不想使用any

二、any类型在特定高级用例中的必要性

(一)实现 ReturnType 工具类型

  1. 首先尝试不用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
  1. 但一旦添加参数就不行了:
const myFunction = (input: string) => {
  console.log("Hey!");
};
type Result = ReturnType<typeof myFunction>;
// 会出现类型不满足约束的错误
  1. 即使将函数参数改为input: unknown,也不是我们想要的效果,因为这样创建的 ReturnType 函数只对接受unknown作为参数的函数有效。
  2. 正确的解决方案是使用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的一种安全使用。

(二)处理函数返回类型逻辑

  1. 例如创建一个根据条件返回不同类型的函数:
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"
  1. 但当启用错误提示时会发现问题,TypeScript 似乎没有将条件类型与运行时逻辑匹配起来,hellogoodbye无法从函数中返回。
  2. 可以使用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 版权所有