在flatMap中返回Array似乎会生成错误的TypeScript类型

smdnsysy  于 2023-03-13  发布在  TypeScript
关注(0)|答案(1)|浏览(143)

我假设这是我的用户错误,但是当我写下面的代码时,我期望返回类型是User[],但是TS似乎认为它是User[][],即使我调用了flatMap()。我不确定我的泛型是否搞砸了什么,但是TS似乎没有发现结果将被扁平化一级。实际的输出是正确的。但与推断的类型不匹配。

type User = {
  id: number
  firstName: string
}

type UserResponse = {
  items: User[]
  nextCursor: number
  hasNextPage: boolean
}

type InfiniteData<PageType> = {
  pages: PageType[]
}

const data: InfiniteData<UserResponse> = {
  pages: [
    {
      items: [
        {id: 1, firstName: 'Bob'},
        {id: 2, firstName: 'Alice'}
      ],
      nextCursor: 1,
      hasNextPage: false
    }
  ]
}

function useInfiniteData<TData, TValue>(
  data: InfiniteData<TData>,
  selectFn: (page: TData) => TValue
) {
  return data.pages.flatMap((page) => {
    // Returns a User[]
    return selectFn(page)
  })
}

// Expect User[] because flatMap flattens by one level,
// but TS thinks it's User[][]
const result = useInfiniteData(data, (page) => page.items)
// Output:
// result = [{
//   "id": 1,
//   "firstName": "Bob"
// }, {
//   "id": 2,
//   "firstName": "Alice"
// }]

这里是一个TSPlayground链接,如果这有助于调试。感谢任何建议!

mwkjh3gx

mwkjh3gx1#

现在的情况是,Typescript从函数的声明中推断出函数的返回类型,它不是在每个调用点推断出不同的返回类型,而是有一个通用的返回类型,然后在每个调用点通过插入该调用点的类型参数来实现。
函数的返回类型被推断为TValue[],因为你用参数selectFn调用flatMap,它返回TValue。仅仅看一下函数声明,Typescript就无法知道TValue是数组类型,实际上签名也不要求它是数组类型。也许聪明的编译器可以推断TValue extends (infer U)[] ? U[] : TValue[]为返回类型。或者类似的东西,但是flatMap没有具有这样的签名的重载,并且Typescript在推理期间没有发明新的条件类型。
然后在调用点,参数TValue被推断为User[],因为selectFn参数返回的是User[],因此返回类型TValue[]最终为User[][],这不是您想要的。
最简单的解决方案是声明selectFn返回值或数组:

function useInfiniteData<TData, TValue>(
  data: InfiniteData<TData>,
  selectFn: (page: TData) => TValue[] | TValue
) {
   // ...
}

Playground链接

相关问题