编程前端/全栈开发者

ReturnType<T> 在 TypeScript 中是如何工作的?它与手动推断函数返回值类型有什么不同?使用它有什么风险/收益?

用 Hintsage AI 助手通过面试

答案。

问题历史

随着 TypeScript 的发展,自动提取函数返回值类型的需求逐渐增大,特别是在大型项目中涉及多个相互关联的函数时。为此,引入了实用类型 ReturnType<T>,该类型自 TypeScript 2.8 版本起出现在标准库中。

问题

在大型和复杂的项目中,保持类型的及时性可能是一个挑战——如果手动指定每个函数的返回值类型,并且签名发生变化,就很容易出现不一致的情况,即签名和实现不匹配。此外,如果函数返回复杂结构,通常也不容易在所有使用位置手动同步返回值类型的描述。

解决方案

实用类型 ReturnType<T> 自动提取函数的返回类型,并可以在代码的任何位置用于类型化函数调用的结果。这降低了手动维护类型基础设施的工作量,并最小化了由于描述和实际返回值类型不一致而导致的错误。

代码示例:

function createUser(name: string, age: number) { return { name, age, created: new Date() }; } type User = ReturnType<typeof createUser>; // User: { name: string; age: number; created: Date; }

关键特性:

  • 自动提取函数的返回值类型(包括泛型),消除了代码重复。
  • 在函数签名或返回值结构更改时降低错误的可能性。
  • 不适用于函数重载——只提取一般(广泛)返回值类型。

有陷阱的问题。

能否将 ReturnType 用于类的方法?

可以,但要记住上下文:如果方法是与函数相关的对象的一个属性,请使用 ReturnType<obj['方法']>。

代码示例:

class MyClass { foo(x: number) { return x * 2; } } type FooReturn = ReturnType<MyClass['foo']>; // 类型错误! // 应这样: type FooReturn = ReturnType<(x: number) => number>; // number // 或将方法提取为函数: const obj = new MyClass(); type FooReturn2 = ReturnType<typeof obj.foo>;

ReturnType 对于 void/never 的函数会返回什么?

对于声明返回类型为 void 的函数,ReturnType 将返回 void。对于 never 类型 — 将返回 never。

代码示例:

function doNothing(): void {} type Result = ReturnType<typeof doNothing>; // void

ReturnType 能否与函数重载一起使用?

不能,ReturnType 提取的是 "实现" 的返回类型,而不是所有重载的返回类型。如果有多个重载,则选择描述的实现类型。

代码示例:

function func(x: number): number; function func(x: string): string; function func(x: any): any { return x } type RT = ReturnType<typeof func>; // any

类型错误和反模式

  • 将 ReturnType 用于重载函数会导致意外的类型。
  • 忽视 ReturnType 不计算 Promise 的返回类型 — 需要 Awaited 或手动处理。
  • 完全依赖 ReturnType 更改函数逻辑而没有更新其他依赖的代码部分。

生活中的示例

消极案例

在项目中为函数返回对象声明了手动类型。函数发生变更 — 类型未更新,调用在运行时失败。

优点:

  • 类型易于阅读,可以手动调整。

缺点:

  • 很快过时,出现类型与实际 API 之间的 "漂移"。

积极案例

转向在使用函数返回值的所有地方使用 ReturnType。在更改的情况下,类型始终是最新的。

优点:

  • 最小化重复,类型化与实际实现保持一致。

缺点:

  • 新手可能会对类型魔法感到困惑。