我有以下情况。
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
以外的密钥的对象。但是我也希望在将它们合并在一起时得到结果对象的类型。
答案 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推断类型参数A
和B
的最简单方法是使a
和{{类型的输入参数b
和A
成为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
类型的类型。例如,如果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}
这是你想要的。因此,您可以获得与预期函数相同的行为,也可以使用类型参数推断!好的,希望有所帮助。祝你好运。