3
type ItemNew = { text: string };
type ItemExist = { text: string, id: number };
 
function fn(
  itemsNew: Array<ItemNew>,
  itemsExist: Array<ItemExist>
) {
  const items = [...itemsNew, ...itemsExist];
  // const items: ItemNew[]
}

Why items is ItemNew[] and not Array<ItemNew | ItemExist>? It seems that information about the wide type (ItemExist) is simply lost.

Alireza Ahmadi
  • 8,579
  • 5
  • 15
  • 42
Dartess
  • 53
  • 3

1 Answers1

2

Consider this example:

type ItemNew = { text: string };
type ItemExist = { text: string, id: number };

type Union = ItemNew | ItemExist
declare var union: Union

const elem = union.text // only text property is allowed

Because text is common for both items you are allowed to get text property.

Because nobody knows whether var union contains id or not. Allowing id prop in this case will be unsound (might cause runtime error).

Let's go back to your example:

type ItemNew = { text: string };
type ItemExist = { text: string, id: number };

function fn(
    itemsNew: Array<ItemNew>,
    itemsExist: Array<ItemExist>
) {
    const items =  [...itemsNew, ...itemsExist];
}

In fact items is a union of Array<ItemNew> | Array<ItemExist>. Same rule is applied. Property text is the only one safe property.

If you want to be able to get id proeprty you might wonna use this helper:

type ItemNew = { text: string };
type ItemExist = { text: string, id: number };

// credit goes to https://stackoverflow.com/questions/65805600/type-union-not-checking-for-excess-properties#answer-65805753
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 fn(
    itemsNew: Array<ItemNew>,
    itemsExist: Array<ItemExist>
) {
    const items: Array<StrictUnion<ItemNew | ItemExist>> = [...itemsNew, ...itemsExist];
    items[0].text // ok
    items[0].id // number | undefined
}

  • Your example and mine are different. In your case, it remains possible to refer to the id with the help of a typeguard, for example: `if ('id' in union) { const elem = union.id }`. In my case, the information about the id is completely lost. – Dartess Sep 08 '21 at 11:40
  • 1
    It seems like my problem is easier to solve like this: `const items: Array = [...itemsNew, ...itemsExist]`, because I don't need to mix types in the end. But thanks for the solution with StrictUnion! – Dartess Sep 08 '21 at 11:41
  • @Dartess yes, seems like an overengineering from my side :D – captain-yossarian from Ukraine Sep 08 '21 at 12:11