打字稿 - 排除参数中其他类型的键



我有以下情况。

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中的键的对象。但是我还想在将它们合并在一起时获取结果对象的类型。

这可以通过在键上使用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;

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


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

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

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

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}

现在你可能会抱怨这并不能阻止你ba有重叠的键,这就是你问题的重点。

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重叠的键都必须具有一种never。 例如,如果A{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}

这仍然可以按预期工作。 但以下内容失败并显示错误

const bad = foo({ a: "" }, { a: 3 }); // error in second argument
// types of property 'a' are incompatible. 
// 'number' is not assignable to type 'never'

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

最新更新