Home > Net >  Typescript - Why are objects with optional values still optional in if check?
Typescript - Why are objects with optional values still optional in if check?

Time:12-15

I saw this at work recently and I was wondering why Typescript doesn't understand the code below. If test is either undefined or a string, shouldn't it understand that it has to be a string if it's in an if statement?

interface IObj {
  test?: string;
}

const obj: IObj = {
  test: "why does this not work?",
};

if (obj.test) {
  const { test } = obj;

  () => testFunc(obj.test); // error (why does this not work?)
  () => testFunc(test); // works 
}

function testFunc(testString: string) {
  return testString;
}
  1. I create an object (obj) with an optional value "test".
  2. I check the optional value in an if.

Destructuring works, but not if you use the object and value right away.

CodePudding user response:

An attribute with ? (like test in IObj) has the possibility of being undefined, so the type of test is string | undefined.

When you check for the existence of the test attribute in your if statement, you are explicitly saying that test must not be undefined to enter the block. Accessing obj.test or your newly created variable test should return a string.

However, the problem is that you are using obj.test inside an arrow function, which creates a new scope and loses the validation you did earlier. Basically, for typescript, there's no way to guarantee that obj.test won't be undefined when the function is called.

This problem does not occur with the test variable because when you create it inside the if statement, the obj.test is a string, so test is also a string, with no possibility of undefined.

Consider the following example:

if (obj.test) {
  const { test } = obj;

  setTimeout(() => {
    testFunc(obj.test);
    testFunc(test);
  }, 5000);

  obj.test = undefined;
}

setTimeout also creates another scope and, as you might know, it will execute the function after X milliseconds (5 seconds in this case). Since it's not blocking, the flow of execution will reach obj.test = undefined before testFunc(obj.test) within setTimeout. This is an example of a problem that can occur.

CodePudding user response:

The assignment into a new variable takes over the type that is possible. In this case string, because we checked for undefined before. Since obj.test still has the type string|undefined and does not "disappear" the error occurs.

So that you can still use directly the value, you can suffix it with ! (obj.test!).

CodePudding user response:

Since typescript 4.9 you can use the new satisfies operator instead of the type-assertion:

interface IObj {
  test?: string;
}

const obj = {
  test: "why does this not work?",
} satisfies IObj;

if (obj.test) {
  const { test } = obj;

  () => testFunc(obj.test); // works
  () => testFunc(test); // works 
}

function testFunc(testString: string) {
    console.log(testString);
  return testString;
}

Playground Example

  • Related