In the following code, the only difference between getDocsWithIdThatWorks and getDocsWithIdThatComplains is the callback they are passing to Array.prototype.map. In one case it's a function expression, and in the other it's an overloaded, generic function. In the first case, TypeScript perfectly understands and agrees about the return type of the overloaded function. But when passing the overloaded function's reference directly as an argument to Array.prototype.map, TypeScript suddenly infers the return type as DocumentWithId<unknown>, despite the fact that the array being mapped is clearly typed as QueryDocumentSnapshot<T>. Why is that so?

Playground link

interface DocumentSnapshot<T> {
    id: string
    data(): T | undefined
}

interface QueryDocumentSnapshot<T> extends DocumentSnapshot<T> {
    data(): T
}

interface DocumentWithId<T> {
  id: string
  data?: T
}

interface QueryDocumentWithId<T> {
  id: string
  data: T
}


function withId<T>(document: QueryDocumentSnapshot<T>): QueryDocumentWithId<T>
function withId<T>(document: DocumentSnapshot<T>): DocumentWithId<T>
function withId<T>(document: DocumentSnapshot<T> | QueryDocumentSnapshot<T>): DocumentWithId<T> | QueryDocumentWithId<T> {
  return {
    id: document.id,
    data: document.data(),
  }
}

type Query<T> = () => Promise<QueryDocumentSnapshot<T>[]>

async function getDocsWithIdThatWorks<T>(query: Query<T>): Promise<QueryDocumentWithId<T>[]> {
  const result = await query()
  return result.map((d) => withId(d))
}

async function getDocsWithIdThatComplains<T>(query: Query<T>): Promise<QueryDocumentWithId<T>[]> {
  const result = await query()
  return result.map(withId)
}

推荐答案

This is a known design limitation of TypeScript; see microsoft/TypeScript#35501. TypeScript can only properly resolve call signatures for overloaded functions when such functions are actually called.

When you write d => withId(d), you are actually calling withId with a parameter of type QueryDocumentSnapshot<T>, and the compiler selects the first call signature, as desired.

But when you just pass withId to map(), you are not calling withId and so the proper overload resolution does not happen. Instead the compiler just "gives up" and picks the last signature. And that results in the error you're seeing.

This happens whenever the compiler has to infer a type involving an overloaded function that it does not call. I don't see direct documentation about this for generic type inference, but it is mentioned in the documentation about conditional type inference, where it says:

When inferring from a type with multiple call signatures (such as the type of an overloaded function), inferences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not possible to perform overload resolution based on a list of argument types.

Javascript相关问答推荐

node 管道的类型错误 [ERR_INVALID_ARG_TYPE]

无法使用 D3 和 React 在 useEffect 挂钩中进行画笔交互并设置状态

用jquery删除表内的行

为什么 Array.some() 方法表现不同?

基于 Javascript 中的标签过滤表

带有 redux 工具包的 Redux:UI 没有改变

通过 :even 和 :odd 为 querySelectorAll 提供索引的替代方法

Javascript .createElement & .appendChild 问题(包括 for 和 if)

react 从列表中删除复选框项目,奇怪的行为

如何完全隐藏 Link React 组件?

React.js:单击列表中的项目时如何更改图标?

在 Javascript 中重建/验证 Ruby on Rails Bcrypt 密码哈希

为什么 javascript 渲染已移除的组件

用于大小数字的 Javascript 计数器

JavaScript,按特定键的值过滤对象数组

如何使多个依赖下拉输入更有效?

react 组件和作为react 组件的功能有什么区别

几个跟踪不同元素的交叉路口观察者合并为一个

Typescript :将元组映射到联合类型似乎在嵌套对象中不起作用

C# .NET AES 与 JavaScript 的互操作性