Typeguards enable us to process function arguments based on their types.
Consider this example -
function join(a: string): string {
return a;
}
console.log(join("hello")); // hello
What if we want to use join for strings or numbers? We can do a union type like so -
function join(a: string | number): string | number {
return a;
}
console.log(join("hello")); // hello
console.log(join(1)); // 1
But, a real world problem would lie in processing the input arguments - not just returning them. Also, the methods and properties of the argument will change within a function based on their types. Typescript solves this problem using Typeguard.
Typeguard is a way if isolating types within an argument in specific blocks. Typescript provides different ways of solving this problem in different situations - typeof, instanceof, literals and in. You can also go ahead and create your own typeguards if none of those satisfy your use case.
typeof
We can build on the previous example -
function join(a: string | number, b: string | number): string | number {
if (typeof a == "string" && typeof b == "string") {
return "".concat(a, " ", b);
} else if (typeof a == "number" && typeof b == "number") {
return "".concat("".concat(String(a), String(b)));
}
}
console.log(join("hello", "world")); // hello world
console.log(join(1, 2)); // 12
By simply the process of checking the type with typeof a == "string", Typescript will infer that anything in the block will use a as a string. So, you can easily use a.toUpperCase() within the block but a.toFixed(0) will produce a compilation error.
In the above code block, you could drop the else if and include just an else to get the same result.
function join(a: string | number, b: string | number): string | number {
if (typeof a == "string" && typeof b == "string") {
return "".concat(a, " ", b);
} else {
return "".concat("".concat(String(a), String(b)));
}
}
console.log(join("hello", "world")); // hello world
console.log(join(1, 2)); // 12
Typescript will understand that the only other type option for a and b is a number. So it will apply number properties and methods to the variables within the else block.
instanceof
Similar to the use-case for variables described above, instanceof applies itself to user-defined classes and objects.
class Earth {
color = "blue";
}
class Mars {
color = "red";
}
function colorPlanet(planet: Earth | Mars) {
if (planet instanceof Earth) {
console.log(planet.color);
} else {
console.log(planet.color);
}
}
colorPlanet(new Earth()); // blue
colorPlanet(new Mars()); // red
in
in is similar to instanceof but rather than using an instance of the class, we use a prop of the object to distinguish between two types.
class Earth {
life = true;
color = "blue";
}
class Mars {
color = "red";
}
function processPlanet(planet: Earth | Mars) {
if ("life" in planet) {
console.log(planet.color);
} else {
console.log(planet.color);
}
}
processPlanet(new Earth()); // blue
processPlanet(new Mars()); // red
literals
Literals just refer to literal values within an object and to use the possible options as typeguards.
type custHappy = "yes" | "no";
function loadSurvey(sat: custHappy) {
if (sat == "yes") {
console.log("happy face");
// loadPublicSurvey();
//upSellHell();
} else {
console.log("frown face");
// loadPrivateSurvey();
}
}