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;
}
- I create an object (obj) with an optional value "test".
- 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;
}