【问题标题】:Complicated generic typing for TypescriptTypescript 的复杂泛型类型
【发布时间】:2020-03-04 10:00:40
【问题描述】:

我一直在拼命想了解如何使函数类型安全,但一直没能做到。该函数应该接受一个对象并返回一个数字。这是一个非常简单的例子(对于我的实际应用,接口更复杂)。

interface Parent {
  id: number;
  children: Child[];
}

interface Child {
  text: string;
}

const parents: Parent[] = [
  {
    id: 1, 
    children: [
      {text: 'Child 1a'}, {text: 'Child 1b'}, 
      {text: 'Child 1c'}, {text: 'Child 1d'}, 
      {text: 'Child 1e'}
    ]
  },
  {
    id: 2, 
    children: [
      {text: 'Child 2a'}, {text: 'Child 2b'}, {text: 'Child 2c'}
    ]
  }  
];

function getMaxNumChildren<T>(data: T[], childKey: keyof T) {
  return data.reduce((max: number, parent: T) => {
    return max > parent[childKey].length ? max : parent[childKey].length;
  }, 0);
}

console.log(getMaxNumChildren<Parent>(parents, 'children')); // 5

因此,您可以想象,parent[childKey].length 会引发错误,因为 typescript 实际上并不知道 T[keyof T] 是一个数组。

我尝试过强制转换为any[] 以及其他一些随机的东西,但我似乎无法做到这一点并保持函数纯粹是通用的。有什么想法吗?

【问题讨论】:

  • 只要您确定childKey 将始终指向一个数组,您就可以将parent 转换为一个数组:(parent[childKey] as Array&lt;any&gt;).length。或者您可以在运行 reduce 之前进行类型检查以确保 length 存在。或者也许定义一个父类型 T 扩展它有一个用字符串索引的数组?

标签: typescript


【解决方案1】:

我能想到的最简单的方法是使函数在K 中通用,childKey 的类型,并注释 data 是一个对象数组,其中包含 K 中的键和属性带有数字 length 属性,如下所示:

function getMaxNumChildren<K extends keyof any>(
  data: Array<Record<K, { length: number }>>,
  childKey: K
) {
  return data.reduce((max, parent) => {
    return max > parent[childKey].length ? max : parent[childKey].length;
  }, 0);
}

然后编译器能够验证parent[childkey] 有一个数字length 并且没有错误。然后你这样称呼它:

console.log(getMaxNumChildren(parents, 'children')); // 5

请注意,您不再调用getMaxNumChildren&lt;Parent&gt;(...),因为泛型类型是键类型,而不是对象类型。如果您愿意,可以致电getMaxNumChildren&lt;"children"&gt;(...),但我会让类型推断在这里为您工作。


希望对你有用。如果它不适用于您的用例,请考虑编辑问题以包含更多详细信息。祝你好运!

Link to code

【讨论】:

    【解决方案2】:

    你需要让 TypeScript 知道两种泛型类型,而不仅仅是一种。第一个是某个键,第二个是某个对象,其中该键的值是数组。

    试试这样的:

    function getMaxNumChildren<TKey extends string, TObj extends { [key in TKey]: unknown[] }>(data: TObj[], childKey: TKey) {
        // ...
    }
    

    【讨论】:

      【解决方案3】:

      你太复杂了。只需使用父数组

      function getMaxNumChildren<T>(data: Parent[]T[], keyGetter: (obj: T) => Array<unknown>) {
        return data.reduce((max: number, parent: ParentT) => {
          return Math.max > (keyGetter(parent.children).length ?, max : parent.children.length;);
        }, 0);
      }
      


      更新答案

      使用回调比滥用类型系统更好。

      function getMaxNumChildren<T>(data: T[], keyGetter: (obj: T) => Array<unknown>) {
        return data.reduce((max: number, parent: T) => {
          return Math.max(keyGetter(parent).length, max);
        }, 0);
      }
      

      你可以这样使用它:

      console.log(getMaxNumChildren<Parent>(parents, (p) => p.children));
      

      【讨论】:

      • 如果这是我必须与之交互的唯一界面,那么是的,这很容易。但是我需要在我的应用程序的多个地方使用完全不同的各种对象来执行此操作。所以getMaxNumChildren函数需要是通用的。
      • @exk0730 我明白了。
      猜你喜欢
      • 2019-05-30
      • 1970-01-01
      • 1970-01-01
      • 2021-04-19
      • 2021-06-23
      • 2020-02-13
      • 2021-12-06
      • 2019-10-06
      • 2019-09-17
      相关资源
      最近更新 更多