在TypeScript中,如何推断参数并施加约束,使所有项均为T的键,其中T是函数的通用类型?

问题描述

我的目标是创建一个名为getFields函数。该函数具有通用的<T>和参数...fields: Array<keyof T>。我希望此函数返回一个函数,当给定类型为<T>的对象时,该函数将返回仅具有...fields中命名的属性的简化对象。

以下是一种帮助程序类型和我的getFields实现:

type SubObj<T,S extends Array<keyof T>> = Pick<
  T,keyof { [K in S[number]]: K extends keyof T ? K : never }
>;

export function getFields<T extends Record<string,unkNown>>(
  ...fields: Array<keyof T>
): (obj: T) => SubObj<T,typeof fields> {
  return (obj: T) =>
    Object.fromEntries(fields.map((field) => [field,obj[field]])) as SubObj<
      T,typeof fields
    >;
}

我用以下代码测试了此实现:

type A = {
  a: string;
  b: string;
  c: string;
};

const b = getFields<A>('a','c')({ a: '',b: '',c: '' });

但是,当我看着typeof b时,它就是Pick<A,"a" | "b" | "c">。我真正想要的是Pick<A,"a" | "c">

我已经尝试了很多事情来使这项工作达到预期的效果,但是唯一的成功是添加了第二个通用参数,这需要我将代码更改为此:

const b = getFields<A,['a','c']>('a',c: '' });

这对我来说太多余了,无法接受。

在这一点上,我认为我已经达到了TypeScript功能的极限,因为我想不出任何其他方法来实现自己想要的东西。

使用TypeScript甚至可以做到吗?如果是这样,我该怎么办?

解决方法

TypeScript当前不支持部分类型参数推断(请参见microsoft/TypeScript#26242)。如果在类型/函数中具有多个类型参数,则需要显式地指定它们,或者让它们全部被推断。没有办法指定一个并让另一个推断出来。如果您想走这条路,则有一些解决方法,但是此问题将成为其他此类问题的重复,我可以例如指向this answer进行操作。

不过,我要退一步说,这里的示例代码可能以更简单的方式表达,根本不需要任何人指定任何类型。考虑一下:

export function getFields<K extends PropertyKey>(
    ...fields: K[]
) {
    return <T extends Record<K,unknown>>(obj: T) =>
        Object.fromEntries(fields.map((field) => [field,obj[field]])) as Pick<T,K>;
}

在这里,我们将从T的呼叫签名中完全删除对象类型getFields()getFields()唯一关心的就是获取类似键的参数的列表。然后,它返回一个也是通用的函数,并且 this 关心对象类型T,并用{{ 1}}。因此,KT都是不可推断的:

K

由于const b = getFields('a','c')({ a: '',b: '',c: '' }); /* const b: Pick<{ a: string; b: string; c: string; },"a" | "c"> */ 的返回值是通用函数,因此它可以用于getFields()以外的类型,例如:

A

但是如果您给它一些不适当的地方,还是应该出错:

const c = getFields('a','c')({ a: 1,b: 2,c: 3 }) // {a: number,c: number}

如果您真的很想指定const d = getFields('a','c')({ a: 1 }) // error! property 'c' is missing ,则可以在调用return函数时做到这一点:

T

如果出于某些原因确实想要在开头指定const e = getFields('a','c')<A>({ a: 1,c: 3 }); // error! number is not string 并推断T,则需要使用一种变通办法来进行部分类型推断,例如更多的咖喱:

K

Playground link to code