You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中如何合法将OnlyFoo & Bar赋值给Foo & Bar?

How to Constrain Foo to Safely Merge Omit<Foo, keyof Bar> and Bar into Foo & Bar

Let's break down your problem first using the subtraction analogy you mentioned: you're treating OnlyFoo = Omit<Foo, keyof Bar> as the "difference" between the "minuend" Foo and "subtrahend" Bar. Then you want to merge this difference back with Bar to get Foo & Bar, but TypeScript is throwing an error because Foo could be instantiated with a subtype that breaks the assignment.

Why the Error Happens

Let's use a concrete example to see what problematic subtype TypeScript is worried about. Suppose we have:

type Foo = { x: "a" };
type Bar = { x: string };

type OnlyFoo = Omit<Foo, keyof Bar>; // Results in {}

If you merge an empty OnlyFoo object with a Bar like { x: "b" }, you get { x: "b" }. But Foo & Bar simplifies to { x: "a" } (since "a" & string is just "a"). Clearly, { x: "b" } can't be assigned to { x: "a" }—that's the invalid scenario TypeScript is guarding against.

The core issue is that Foo might have properties overlapping with Bar that are narrower than Bar's corresponding properties. When you merge OnlyFoo (which drops those overlapping properties) with Bar, you end up with Bar's wider type for those properties, which doesn't match the narrower type in Foo & Bar.

The Fix: Constrain Foo to Be a Supertype of Bar's Overlapping Properties

We need to add a generic constraint that ensures any properties shared between Foo and Bar are compatible—specifically, that Bar's properties can be assigned to Foo's corresponding properties (i.e., Foo's shared properties are supertypes of Bar's).

Here's how to write the constrained factory function:

function merge<Foo, Bar extends Pick<Foo, keyof Bar>>(
  fooOnly: Omit<Foo, keyof Bar>,
  bar: Bar
): Foo & Bar {
  return { ...fooOnly, ...bar } as Foo & Bar;
}

Let's unpack the constraint Bar extends Pick<Foo, keyof Bar>:

  • Pick<Foo, keyof Bar> extracts all properties from Foo that exist in Bar.
  • The constraint says that Bar must be assignable to this extracted type. In plain terms: every property in Bar must match (or be a subtype of) the same property in Foo.

Testing the Fix

Let's test with the problematic example from earlier:

type Foo = { x: "a" };
type Bar = { x: string };

// Now TypeScript throws an error HERE, before even calling merge!
// Because Bar (x: string) doesn't extend Pick<Foo, keyof Bar> (x: "a")
const bar: Bar = { x: "b" };
merge({}, bar); // Error: Type 'string' is not assignable to type '"a"'

This is good—TypeScript now catches the incompatibility at the constraint level, instead of letting an invalid assignment slip through.

For valid cases where the constraint is satisfied:

type Foo = { x: string; y: number };
type Bar = { x: "hello" };

type OnlyFoo = Omit<Foo, keyof Bar>; // { y: number }

const fooOnly: OnlyFoo = { y: 42 };
const bar: Bar = { x: "hello" };

const merged = merge(fooOnly, bar); // Type is Foo & Bar = { x: "hello"; y: number }
// No error—this works perfectly!

Why This Works

When Bar extends Pick<Foo, keyof Bar> holds, Foo & Bar simplifies to Omit<Foo, keyof Bar> & Bar. Because:

  • Foo & Bar combines Foo's non-overlapping properties, plus the intersection of Foo's overlapping properties and Bar's overlapping properties.
  • Since Bar's overlapping properties are subtypes of Foo's, their intersection is just Bar's properties.
  • So Foo & Bar is exactly the type you get by merging Omit<Foo, keyof Bar> and Bar, making the assignment valid.

内容的提问来源于stack exchange,提问作者totokaka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 23:27:36