To explain the reasoning a little deeper than @Alexandre Dias's example...
The &
operator is known as the intersection type and basically combines or merges two types as best as it can π. This is similar to the union type but the union is an OR
operation rather than an AND
operation.
From the docs it says...
type Combined = { a: number } & { b: string };
type Conflicting = { a: number } & { a: string };
Combined has two properties, a and b, just as if they had been written as one object literal type. Intersection and union are recursive in case of conflicts, so Conflicting.a: number & string.
The important thing to note is the part about it merging the types recursively. Thus in your case, A & B
would result is bar
being of type number & string
, which does not make sense and typescript converts this to never
.
So any value you set as bar
will throw a type error.
So yes the simplest solution to you case would be to just remove (i.e. Omit
) the duplicate keys from one of the types before intersecting...
type CombinedAB = Omit<A, keyof B> & B
// π hack to just look at types
const test2: CombinedAB = {} as any;
test2.bar = 'string' // ok
See demo typescript playground here
π¨π¨π¨ Helpful advice...
An INCREDIBLY useful tool for troubleshooting typescript types is piotrwitek/utility-types
. This is a library of dozens of useful types to build off of the native built-in typescript types. I would highly advise adding this as a devDependencey
to use whenever you need. It includes many improvements to native types as well like allowing keys for Required<T, K>
.
The type that would solve your issue here is Overwrite<T, U>
(From U overwrite properties to T), then you can simply go into their src code and look at how the type works...
/**
* Overwrite
* @desc From `U` overwrite properties to `T`
* @example
* type Props = { name: string; age: number; visible: boolean };
* type NewProps = { age: string; other: string };
*
* // Expect: { name: string; age: string; visible: boolean; }
* type ReplacedProps = Overwrite<Props, NewProps>;
*/
export type Overwrite<
T extends object,
U extends object,
I = Diff<T, U> & Intersection<U, T>
> = Pick<I, keyof I>;
Note: Diff
and Intersection
are two of their custom types.
Looking at how they effectively use more complex typescript types like conditionals and infer
lets you understand and create even the most complex types like DeepPartial
Pick<T, Exclude<keyof T, 'addresses'>>
can be written asOmit<T, 'addresses'>
builtin too β Shelia