Why does TypeScript refinement work differently when declaring type as union vs optional?

83 views Asked by At
type Dialog1 = { id?: string };

type VisibleDialog = { id: string };
type DestroyedDialog = {};
type Dialog2 = VisibleDialog | DestroyedDialog;

function remove1(dialog: Dialog1) {
  if (!dialog.id) {
    return;
  } 
  //(1)
  document.getElementById(dialog.id); //dialog:Dislog1
  setTimeout(() => {
    //Argument of type 'string | undefined' is not
    //assignable to parameter of type 'string'.
    document.getElementById(dialog.id);
  });
}

function remove2(dialog: Dialog2) {
  if (!("id" in dialog)) {
    return;
  }
  document.getElementById(dialog.id); //dialog:VisibleDialog
  setTimeout(() => {
    document.getElementById(dialog.id); //dialog:VisibleDialog
  });
}

The above two functions(remove1 and remove2) have exactly the same meaning from a human point of view while being treated differently by typescript. This does not make sense to me, here is how I reason it about

For remove1 after (1), ts know id is sure to exist, so document.getElementById(dialog.id) is OK because it is in the same event loop, but the refinement does not carry out to a new scope(setTimeout). Such behavior makes sense to me, dialog.id is still subject to change after the check due to the event loop

However, for remove2, there is no ts error because the refined type (Dislog2-> dialog:VisibleDialog) is carried out to the setTimeout. Such behavior is literally wrong, i.e. run time error

Actually, I can not confidently understand how typescript think about the code, could anyone walk it through or point some related material to me, especially about typescript's refinement and scope.

0

There are 0 answers