TypeScript Guard Condition Improvements with version 4.4
Posted on: January 10, 2022
TypeScript allows variables to be of several types. For example, a function's parameter can be a number or a string. Or another example can be a variable within a function can be a union of several types.
1function transform(arg: number | string) {2 let result = "";3 if (typeof arg === "string") {4 result = arg.toLowerCase();5 } else {6 result = arg.toString();7 }8 return result;9}
However, if you wanted to have the condition inside a variable before version 4.4, the following code would not compile. This is because the transpiler would have given an error saying that the arg
variable is of type number | string
inside the if
statement.
1function transform(arg: number | string) {2 let result = "";3 const isString = typeof arg === "string";4 if (isString) {5 result = arg.toLowerCase();6 } else {7 result = arg.toString();8 }9 return result;10}
However, there is a catch, the variable that is holding the boolean value must be const
. It means that the following code does not transpile:
1function transform(arg: number | string) {2 let result = "";3 let isString = typeof arg === "string";4 if (isString) {5 result = arg.toLowerCase();6 } else {7 result = arg.toString();8 }9 return result;10}
Neither the following one:
1const isString = true;2function transform(arg: number | string, isString: boolean) {3 // ...4}
Or that one:
1const isString = true;2function transform(arg: number | string, isString: Readonly<boolean>) {3 // ...4}
Overall, this is useful, but you still will not have a function that handles the condition for you to be reusable across your system.
1function transform(arg: number | string) {2 let result = "";3 const isString = isItAString(arg);4 if (isString) {5 result = arg.toLowerCase();6 } else {7 result = arg.toString();8 }9 return result;10}1112function isItAString(arg: number | string): boolean {13 return typeof arg === "string";14}
The concept of having the type outside the direction condition also extends to discriminant type. In the following example, we have two interfaces with a common type name with a different value: type
. We can check the type
, and TypeScript will know depending on the value of the check is true for a Human
or an Animal
. Before TypeScript 4.4, it was impossible to store the value inside a variable. Now, it is possible. It means that TypeScript carries the information about the check and the information about the interface. In the example, when the condition is true, the autocomplete will specify only the property of the interface belonging to the right interface. In the first if
the livingSpecie
will have the legs
and networth
available only.
1interface Human {2 type: "human";3 legs: number;4 networth: number;5}67interface Animal {8 type: "Animal";9 legs: number;10 sound: string;11}1213function showUniqueInformation(livingSpecie: Human | Animal): void{14 const isHuman = livingSpecie.type === "human";15 if(isHuman){16 console.log(livingSpecie.networth);17 } else {18 console.log(livingSpecie.sound);19 }20}
The new version of TypeScript also allows more complex cases. For example, imagine that we want to set a value of number or string depending of the type. We need to check both types. We can continue to use the variable to hold the isHuman
, which help to reduce the repetition. Hence, we see the usefulness of the new feature.
1interface Human {2 type: "human";3 legs: number;4 networth: number;5}67interface Animal {8 type: "Animal";9 legs: number;10 sound: string;11}1213function saveUniqueValue(livingSpecie: Human | Animal, value: string | number): void {14 const isHuman = livingSpecie.type === "human";15 if (isHuman && typeof value === "number") {16 livingSpecie.networth = value;17 } else if (!isHuman && typeof value === "string") {18 livingSpecie.sound = value;19 }20}
But we can go a step further and also have the second check-in a variable, and TypeScript will combine the two.
1function saveUniqueValue(livingSpecie: Human | Animal, value: string | number): void {2 const isHuman = livingSpecie.type === "human";3 const isValueHuman = typeof value === "number";4 if (isHuman && isValueHuman) {5 livingSpecie.networth = value;6 } else if (!isHuman && !isValueHuman) {7 livingSpecie.sound = value;8 }9}
It is still important to note that we cannot use else
in the previous code because there is a potential case that we have an Animal
with a number
which would not be legit.