【问题标题】:How to type generic merging function in typescript如何在打字稿中键入通用合并函数
【发布时间】:2020-06-14 07:18:10
【问题描述】:

我有一个通用的 javascript 函数,我在打字稿中无法正确输入。它需要一系列项目。如果这些项目是数组,它会将它们展平为单个数组。如果它们是对象,它将它们合并为一个大对象。否则它只返回原始数组。

function combineResults(responses) {
  if (responses.length === 0) {
    return [];
  }

  if (Array.isArray(responses[0])) {
    return responses.flat(1);
  } else if (typeof responses[0] === 'object') {
    return Object.assign({}, ...responses);
  }
  else {
    return responses;
  }
}

是否可以安全地键入 this,这样如果你传递一个数组数组,你的返回类型将是一个数组,如果你传递一个对象数组,你的返回类型将是一个对象。如果你传递一个既不是数组也不是对象的数组,你的返回类型将是原始数组类型。

【问题讨论】:

    标签: javascript typescript


    【解决方案1】:

    我倾向于给这个overloaded 类型签名对应于每个案例。这里有一些障碍:一个是您只检查responses 的第一个元素并假设其余元素属于同一类型;但数组可以是异构的。由于 JS 和 TS 中的数组被认为是对象,如果你给它一个像 [[1, 2, 3], {a: 1}] 这样的异构数组,combineResults() 的调用签名可能会做一些奇怪的事情。我不知道你想在运行时发生什么,所以我不知道你想在类型签名中看到什么。这些是边缘情况。

    另一个问题是,像 [{a: 1}, {b: ""}] 这样的数组在 TypeScript 中被认为是 Array<{a: number, b?: undefined} | {b: string, a?: undefined}> 类型,并且将其转换为 {a: number, b: string} 涉及大量的类型系统跳跃,包括转换 unions to intersections 和过滤掉undefined 属性。

    所以,这里是:

    type UnionToIntersection<U> =
      (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
    type Defined<T> = T extends any ? Pick<T, { [K in keyof T]-?: T[K] extends undefined ? never : K }[keyof T]> : never;
    type Expand<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
    
    function combineResults<T extends ReadonlyArray<any>>(responses: ReadonlyArray<T>): T[number][];
    function combineResults<T extends object>(responses: ReadonlyArray<T>): Expand<UnionToIntersection<Defined<T>>>;
    function combineResults<T extends ReadonlyArray<any>>(responses: T): T;
    function combineResults(responses: readonly any[]) {
      if (responses.length === 0) {
        return [];
      }
      if (Array.isArray(responses[0])) {
        return responses.flat(1);
      } else if (typeof responses[0] === 'object') {
        return Object.assign({}, ...responses);
      }
      else {
        return responses;
      }
    }
    

    调用签名应将数组数组映射到数组,将对象数组映射到对象,并将任何其他数组映射到自身。让我们测试一下:

    const arrs = combineResults([[1, 2, 3], ["a", "b"]]); // (string | number)[]
    const objs = combineResults([{ a: 1 }, { b: "hey" }]) // {a: number, b: string}
    const nons = combineResults([1, 2, 3]); // number[]
    

    看起来不错,我想。不过要注意边缘情况:

    const hmm = combineResults([[1, 2, 3], { a: "" }])
    /* const hmm: {
        [x: number]: number;
        a: string;
    } ?!?!? */
    

    您可能希望调整这些签名以完全防止异构数组。但这是另一个我现在没有时间去的兔子洞。

    好的,希望对您有所帮助;祝你好运!

    Playground link to code

    【讨论】:

    • 哇,谢谢!这很棒!它真的很好用。由于我的 tsconfig 规则,我最终用未知数替换了一堆 any,但这似乎不会导致问题。我说函数本身的返回类型未知,因为实际类型由重载处理。
    【解决方案2】:

    假设项目是object,这个怎么样:

    function combineResults(responses: Array<object> | Array<Array<object>>): Array<object> | object {
    

    【讨论】:

    • 我不能假设它们是对象。目标是处理数组数组、对象数组或任意项数组。如果它是一个对象数组,而不是数组,则返回值将是一个对象。
    • 那就把object改成any或者泛型
    猜你喜欢
    • 1970-01-01
    • 2022-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多