1

我的目标是创建一个名为: 的函数getFields。这个函数有一个泛型<T>和一个参数...fields: Array<keyof T>。我希望这个函数返回一个函数,当给定一个类型的对象时,该函数<T>将返回一个简化的对象,其中仅包含名为 in 的属性...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')({ a: '', b: '', c: '' });

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

在这一点上,我认为我已经达到了我的 TypeScript 能力的极限,因为我想不出任何其他方法来完成我正在寻找的东西。

这甚至可以用 TypeScript 做吗?如果是这样,我需要做什么?

4

1 回答 1

3

TypeScript 目前不支持部分类型参数推断(请参阅microsoft/TypeScript#26242)。如果你在一个类型/函数中有多个类型参数,你要么需要明确地指定它们,要么让它们都被推断出来。没有办法指定一个并让另一个被推断出来。如果您想走那条路,有一些解决方法,但是这个问题会成为其他此类问题的重复,例如,我可以指出如何继续的这个答案。

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

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()关心的是获得一个类似键的参数列表。然后它返回一个也是泛型的函数,这个函数关心对象类型T,并将其限制为带有来自的键的东西K。两者TK因此是可推断的:

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

如果你真的关心指定T你可以在调用返回函数时这样做:

const e = getFields('a', 'c')<A>({ a: 1, b: 2, c: 3 }); // error!  number is not string

如果出于某种原因您真的T在开头指定并推断K,则需要使用其中一种解决方法进行部分类型推断,例如更多的柯里化:

const stricterGetFields = <T,>() => <K extends keyof T>(...fields: K[]) => (obj: T) =>
    Object.fromEntries(fields.map((field) => [field, obj[field]])) as Pick<T, K>;

const f = stricterGetFields<A>(); // specify here and do nothing else
const g = f('a', 'c')({ a: "", b: "", c: "" }); // Pick<A, 'a'|'c'>; 

Playground 代码链接

于 2020-10-15T17:47:24.253 回答