Typescript - 在参数中排除其他类型的键

时间:2018-04-07 12:42:58

标签: typescript object types

我有以下情况。

function foo<A extends object, B extends A>(
  a: A,
  b: Pick<B, Exclude<keyof B, keyof A>>
): B {
  return undefined as any;
}

const r = foo<{ a: string }, { a: string; b: number }>({ a: "" }, { b: 2 });

我希望b成为只有a以外的密钥的对象。但是我也希望在将它们合并在一起时得到结果对象的类型。

2 个答案:

答案 0 :(得分:2)

这可以通过在密钥上使用Exclude然后在这些过滤的密钥上使用Pick创建最终对象类型来实现:

function foo<A extends object, B extends A>(
    a: A,
    b: Pick<B, Exclude<keyof B, keyof A>>,
): B {
  return undefined as any;
}

再解释一下:

type A = { a: string };
type B = { a: string; b: number };

// "b"
type FilteredProps = Exclude<keyof B, keyof A>;
// { b: number; }
type FinalType = Pick<B, FilteredProps>;

Exclude<B, Pick<B, keyof A>>无效的原因是:

// { a: string; }
type PickedObject = Pick<B, keyof A>;
// never, because { a: string; b: number; } extends { a: string; }
type FinalType = Exclude<B, PickedObject>;

// for reference...
type Exclude<T, U> = T extends U ? never : T;

答案 1 :(得分:2)

我认为@DavidSherret已经回答了你提出的问题,但是我要警惕使用任何泛型函数,其中无法从参数中推断出类型参数。在您的情况下,TypeScript无法从B参数真正推断b,因此您需要在调用foo()时自己明确指定类型参数。如果那是你真正想要的,那很好。否则,请继续阅读:

使TypeScript推断类型参数AB的最简单方法是使a和{{类型的输入参数bA成为1}}分别。例如:

B

执行此操作时,此操作将按预期执行:

declare function foo<A, B>(a: A, b: B): A & B; 

intersection-typed输出相当于const r = foo({ a: "" }, { b: 2 }); // A is inferred as {a: string} // B is inferred as {b: number} // output is therefore {a: string} & {b: number} 。如果您真的需要它完全 {a: string, b: number}而不是等效,您可以使用映射类型来执行此操作:

{a: string, b: number}

现在您可能会抱怨这不会阻止type Id<T> = { [K in keyof T]: T[K] }; declare function foo<A, B>(a: A, b: B): Id<A & B>; const r = foo({ a: "" }, { b: 2 }); // A is inferred as {a: string} // B is inferred as {b: number} // output is {a: string, b: number} b重叠密钥,这是您问题的重点。

a

所以让我们解决一下:

const bad = foo({ a: "" }, { a: 3 }); // not an error!!

现在我们已将function foo<A, B extends { [K in keyof B]: K extends keyof A ? never : B[K] }>( a: A, b: B ): Id<A & B> { return Object.assign({}, a, b); // implementation, why not } 限制为B,这是......某事。让我们分开吧。它与{ [K in keyof B]: K extends keyof A ? never : B[K]}B)具有相同的密钥,对于每个密钥,如果密钥是[K in keyof B]的一部分,则值为keyof A。否则(如果密钥不属于never),则该值与keyof A中的值相同。因此,基本上说B必须限制为与B重叠的任何键必须具有A类型的类型。例如,如果neverA,而{a: string}B,则该映射类型将变为{a: number, b: boolean}。由于{a: never, b: boolean}不会延伸{a: number, b: boolean},因此会失败。也许这是太多的解释。让我们看看它的实际效果:

{a: never, b: boolean}

这仍然可以按预期工作。但是以下失败并出现错误

const r = foo({ a: "" }, { b: 2 }); // {a: string, b: number}

这是你想要的。因此,您可以获得与预期函数相同的行为,也可以使用类型参数推断!好的,希望有所帮助。祝你好运。