Take a look at this scenario:
type ProductNew = { name: string };
type ProductExisting = { name: string, id: number };
type Inventory = ProductNew | ProductExisting
declare var inventory: Inventory
const item = inventory.name // only the name property can be accessed
Since both types have the common property name
, you are allowed to access the name
property.
Because we cannot determine if inventory
contains an id
or not. Allowing access to the id
property in this case could lead to potential runtime errors.
Now let's revisit your initial example:
type ProductNew = { name: string };
type ProductExisting = { name: string, id: number };
function mergeProducts(
newProducts: Array<ProductNew>,
existingProducts: Array<ProductExisting>
) {
const products = [...newProducts, ...existingProducts];
}
In this case, products
is a union of
Array<ProductNew> | Array<ProductExisting>
. The same rule applies here where only the
name
property is considered safe to access.
If you wish to be able to access the id
property as well, you can use the following helper function:
type ProductNew = { name: string };
type ProductExisting = { name: string, id: number };
type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> =
T extends any
? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>
function mergeProducts(
newProducts: Array<ProductNew>,
existingProducts: Array<ProductExisting>
) {
const mergedProducts: Array<StrictUnion<ProductNew | ProductExisting>> = [...newProducts, ...existingProducts];
mergedProducts[0].name // okay
mergedProducts[0].id // number | undefined
}