如何在array参数中获取每个函数的返回结果的合并类型?

时间:2019-07-13 03:49:30

标签: typescript

我希望getInjectData函数根据我传入的injects自动返回每个注入函数的返回值的合并类型。

代码是:

function getInjectData ({ injects }) {
  cosnt data = {}
  for (let inject of injects) Object.assign(data, inject())
  return data
}

function injectUser () {
  return { user: { name: 'Jack' } }
}

function injectBook () {
  return { book: { author: 'Jack' } }
}

const injectData = getInjectData({ injects: [injectUser, injectBook] })

因此,我希望injectData的类型为:

{
  user: {
    name: string
  }

  book: {
    author: string
  }
}

当然,我知道可以通过将泛型传递给getInjectData来实现此要求:

interface InjectData {
  user: {
    name: string
  }

  book: {
    author: string
  }
}
const injectData = getInjectData<InjectData>({ injects: [injectUser, injectBook] })

但是应该有一种自动推测的方法吗?

1 个答案:

答案 0 :(得分:1)

您确实必须帮助编译器解决这一问题。推断变量data的类型为{},该变量太宽。并表示data的预期类型涉及一些跳跃。

这就是我要做的。首先,我将使您的函数具有以下通用性:

function getInjectData<T extends () => object>({ injects }: { injects: T[] }) {}

通过这种方式,编译器期望injects将是产生某些对象类型的无参数函数数组。然后,data实际上应该是所有那些返回的对象类型中的intersection

检查数组的元素时,自然会得到一个并集而不是一个交集。幸运的是,有[一种将联合变成交集的方法](https://stackoverflow.com/a/50375286/2887218 ):

type UnionToIntersection<U> = (U extends any
  ? (k: U) => void
  : never) extends ((k: infer I) => void)
  ? I
  : never;

此外,完成后,您可能会遇到像{a: string} & {b: number} & {c: boolean}这样的丑陋交叉点,而不是像{a: string, b: number, c: boolean}这样的单一类型。您可以使用其他类型的别名将它们转换为:

type Prettify<T> = [T] extends [infer U] ? { [K in keyof U]: U[K] } : never;

(这使用条件类型推断和映射类型来遍历属性并创建单个对象)。

这就是我声明data的方式:

function getInjectData<T extends () => object>({ injects }: { injects: T[] }) {
  const data = {} as Prettify<UnionToIntersection<ReturnType<T>>>;
  for (let inject of injects) Object.assign(data, inject());
  return data;
}

请明确说明:T是返回对象的函数类型的并集。 ReturnType<T>使用built in type alias产生这些返回类型的并集。 UnionToIntersection<ReturnType<T>>是这些返回类型的交集,而Prettify<UnionToIntersection<ReturnType<T>>>是表示该交集的单个对象类型。让我们尝试一下:

function injectUser() {
  return { user: { name: "Jack" } };
}

function injectBook() {
  return { book: { author: "Jack" } };
}

const injectData = getInjectData({ injects: [injectUser, injectBook] });
/*
const injectData: {
    user: {
        name: string;
    };
    book: {
        author: string;
    };
}
*/

对我很好。希望能有所帮助;祝你好运!

Link to code