Reputation: 385
I was writing a code that goes a little bit like this:
function helloWorld(customName: string) {
return `Hello, ${customName}`;
}
const myName: string | null = null;
const isValidName = myName !== null;
if (isValidName) {
console.log(helloWorld(myName));
}
If you run this code in the TypeScript playground, you'll see TypeScript will complain that Argument of type 'null' is not assignable to parameter of type 'string'.
How's that the case, though? That code only runs when isValidName
is truthy and isValidName
can only be true
if myName
is not null
. Hence, myName
is a string
here. Why this doesn't work?
Upvotes: 2
Views: 468
Reputation: 99533
The other answers do a good job explaining workarounds, but don't explain why.
The simple fact is that Typescript is not yet sophisticated enough to understand multiple dependent variables.
It's very possible that in the future features for this will get added. There's no reason why this couldn't work, but I imagine it is simply difficult.
I hope one day this does work, because it also makes some of the stuff I'm doing simpler.
Upvotes: 0
Reputation: 2201
If you want to store the result of narrow downing in isValidName
for further use and don't want to re-compute it, you can create a utility function like this
/**
* @param a The precomputed result
* @param _b The variable to be narrowed down
* @template T The type to which `_b` will be narrowed down to when `a` is true
*/
function typeCheck<T>(a: boolean, _b: any): _b is T {
return a
}
Use like this
if (typeCheck<string>(isValidName, myName)) {
console.log(helloWorld(myName)); // string here
} else {
console.log(myName) // null here
}
This can be helpful when computing long expressions like
if (
isLoneAvailableCapacity(feed) &&
(isAvailableCapacitySubbeamPath(link) || isAvailableCapacityConnectivityLegPath(link)) &&
toAvailableCapacityKey(feed.availableCapacity) === link.availableCapacityKey
) {
// do something
}
// snip....
// some more code
if (
isLoneAvailableCapacity(feed) &&
(isAvailableCapacitySubbeamPath(link) || isAvailableCapacityConnectivityLegPath(link)) &&
toAvailableCapacityKey(feed.availableCapacity) === link.availableCapacityKey
) {
// do something else
}
It can be replaced easily by
const isValidFeed = isLoneAvailableCapacity(feed) &&
(isAvailableCapacitySubbeamPath(link) || isAvailableCapacityConnectivityLegPath(link)) &&
toAvailableCapacityKey(feed.availableCapacity) === link.availableCapacityKey
if(typeCheck<Feed>(isValidFeed, feed)) {
// do something
}
// snip....
// some more code
if(typeCheck<Feed>(isValidFeed, feed)) {
// do something else
}
Upvotes: 0
Reputation: 42218
The way that you have written this, typescript sees that isValidName
is a boolean
constant and that's it.
You, the code author, know that the value of isValidName
indicates something about the value of myName
, but typescript doesn't consider the relationship between the two variables.
What you want is for your if
statement to use a type guard. Essentially a type guard is a boolean value computed at runtime which depends on the value of myName
and whether than boolean is true
or false
provides some information which narrows the type definition of myName
.
As @falinsky pointed out, myName !== null
is fine as a type guard if you use it inside the condition of your if
statement. It says, "if this is true
then myName
is not null
".
You can also create isValidName
as a function which checks a name and determines that it is a string.
const isValidName = (name: string | null): name is string => {
return name !== null;
}
if (isValidName(myName)) {
console.log(helloWorld(myName));
}
Upvotes: 0
Reputation: 7428
As far as I understand - that fact that you're storing the result of myName !== null
into the variable isValidName
enforces the TypeScript to check that value in the runtime and behave appropriately (so calling helloWorld(myName)
seems illegal because isValidName
can be potentially either true
or false
in runtime).
However, if you change the check to be
if (myName !== null) {
console.log(helloWorld(myName));
}
TypeScript will be able to detect that the only possible type of myName
can be a string
in those circumstances.
Upvotes: 2