首页 > 解决方案 > 键入一个接受对象及其属性名称的通用函数,期望 obj[propName] 是某种类型,例如 sumBy、mapBy

问题描述

这是我很抱歉尝试键入 a mapBy

export function mapBy<T, K = keyof T>(array: T[], key: K): T[K][] {
  return array.map((item: T) => item[key]);
}

打字稿抱怨:Type 'K' cannot be used to index type 'T'. ts(2536)

我的第二种情况更棘手:

export default function sumBy<T, K = keyof T>(array: T[], key: K): number {
  return array.reduce((result: number, item: T) => {
    return result + item[key];
  }, 0);
}

在这里,我需要以某种方式告诉 TypeScriptT必须是一个具有numberin的对象T[K]

请帮助我在严格模式下正确键入此函数。

当第一个参数在具有给定名称的属性sumBy()中没有 a 时,我希望将调用标记为类型错误。number

UPD:TS Playground 代码示例

标签: typescripttypescript-generics

解决方案


您面临的主要问题是,如果编译器无法推断,或者如果有人手动指定但没有K = keyof T给出默认规范。没有什么能阻止编译器推断或调用者手动指定一些完全不同的类型,例如,因此编译器真的不能安全地索引with 。Kkeyof TKTKDate | string | Array<RegExp>TK

你想要的是限制使用 语法。(所以,而不是)。这修复了:Kkeyof TK extends keyof Textends=mapBy()

export function mapBy<T, K extends keyof T>(array: T[], key: K): T[K][] {
    return array.map((item: T) => item[key]);
}

因为sumBy()您还需要第二个约束:您想要K extends keyof T,但您还想要T extends Record<K, number>whereRecord<K, V>是一个实用程序类型,这意味着在键(或键的联合)处具有属性的类型,K其值为 type V。所以T extends Record<K, number>意味着T必须在 key 处有一个属性,K其值为 type number。这足以让编译器接受这result + item[key]是一个有效的操作:

export default function sumBy<T extends Record<K, number>, K extends keyof T>(
  array: T[], 
  key: K
): number {
    return array.reduce((result: number, item: T) => {
        return result + item[key];
    }, 0);
}

这具有只允许参数对应于参数类型中每个元素的 -valued 属性的sumBy()调用的预期效果:keynumberarray

const arr = [{ a: 1, b: 2 }, { a: 3, b: "hello" }, { a: 6, c: true }];

console.log(sumBy(arr, "a")) // 10
sumBy(arr, "b") // error! Types of property 'b' are incompatible. string is not number
sumBy(arr, "c") // error! Types of property 'c' are incompatible. undefined is not number

Playground 代码链接


推荐阅读