In the last tutorial we saw union types which can be used to provide multiple type choices for a variable.
We use type guards to differentiate between the possible value of the union types. A type guard is nothing but a boolean expression; if it is true for a target type, the corresponding block gets executed where we apply a desired logic on that type.
Type guards for primitives
The JavaScript typeof operator can be used as type guards for primitives. The operator typeof returns a string indicating the underlying type. This string value must be "number", "string", "boolean", or "symbol".
Example:
function show(x: number | string): void {
console.log("typeof x: " + typeof x);
if (typeof x == 'number') {
console.log("a number: " + x);
} else {
console.log("a string: " + x);
}
}
show("test string");
show(4);
Outputtypeof x: string a string: test string typeof x: number a number: 4
Type guards for objects
The typeof operator for an object always returns 'object'.
class Person {}
let person = new Person();
console.log(typeof person);
console.log(typeof new String("test"));
console.log(typeof null);
Outputobject object object
So how to use type guards for objects? In JavaScript, we typically decide on the object types by checking whether their particular properties/methods are defined or not.
Let's consider following example:
type-guards-example.tsclass Car {
start() {
console.log("car starting");
}
drive() {
console.log("car driving");
}
}
class Bike {
start() {
console.log("bike starting");
}
ride() {
console.log("Bike ridding");
}
}
function move(vehicle: Bike | Car): void {
vehicle.start();
if (vehicle.drive) { //same as if(vehicle.drive!=undefined)
vehicle.drive();
} else {
vehicle.ride();
}
}
move(new Bike()); Outputerror TS5033: Could not write file 'type-guards-example.js': EPERM: operation not permitted, open 'D:\example-projects\typescript\advance-types\typescript-type-guards\type-guards-example.js'. type-guards-example.ts(23,17): error TS2339: Property 'drive' does not exist on type 'Car | Bike'. Property 'drive' does not exist on type 'Bike'. type-guards-example.ts(24,17): error TS2339: Property 'drive' does not exist on type 'Car | Bike'. Property 'drive' does not exist on type 'Bike'. type-guards-example.ts(26,17): error TS2339: Property 'ride' does not exist on type 'Car | Bike'. Property 'ride' does not exist on type 'Car'.
TypeScript only allows access to the members that are guaranteed to be in all the constituents of a union type. To tell TypeScript the types within guards, we need to use type assertion:
type-guards-example2.tsclass Car {
drive() {
console.log("car driving");
}
}
class Bike {
ride() {
console.log("Bike ridding");
}
}
function move(vehicle: Bike | Car): void {
if ((vehicle as Car).drive) {
(vehicle as Car).drive();
} else {
(vehicle as Bike).ride();
}
}
move(new Bike());
OutputBike ridding
User-defined type guard
To avoid several type assertions (specially when there are multiple if/else paths), TypeScript allows us to define our own function which should return a type predicate.
User-defined type guards for objects
Let's rewrite our above example:
type-guards-example3.tsclass Car {
drive() {
console.log("car driving");
}
}
class Bike {
ride() {
console.log("Bike ridding");
}
}
function isCar(vehicle: Bike | Car): vehicle is Car {
return (vehicle as Car).drive != undefined;
}
function move(vehicle: Bike | Car): void {
if (isCar(vehicle)) {
vehicle.drive();
} else {
vehicle.ride();
}
}
move(new Bike()); OutputBike ridding
In function isCar(), vehicle is Car is our type predicate. Type predicate's general syntax: parameterName is Type where parameterName must be the name of a parameter from the current function signature.
Why do we need this new syntax? Can we just return 'boolean' from our isCar() function. The answer is; boolean returned value is only known during the runtime, but the type vehicle is Car (in our example) is a compile time assertion. It is just like a typeof operator that works for objects as well. Let's rewrite our example returning boolean to see what will happen:
type-guards-example4.tsclass Car {
drive() {
console.log("car driving");
}
}
class Bike {
ride() {
console.log("Bike ridding");
}
}
function isCar(vehicle: Bike | Car): boolean {
return (vehicle as Car).drive != undefined;
}
function move(vehicle: Bike | Car): void {
if (isCar(vehicle)) {
vehicle.drive();
} else {
vehicle.ride();
}
}
move(new Bike()); Outputerror TS5033: Could not write file 'type-guards-example4.js': EPERM: operation not permitted, open 'D:\example-projects\typescript\advance-types\typescript-type-guards\type-guards-example4.js'. type-guards-example4.ts(19,17): error TS2339: Property 'drive' does not exist on type 'Car | Bike'. Property 'drive' does not exist on type 'Bike'. type-guards-example4.ts(21,17): error TS2339: Property 'ride' does not exist on type 'Car | Bike'. Property 'ride' does not exist on type 'Car'.
User-defined type guards for primitives
type-of-guards-example2.tsfunction isNumber(x: any): x is number {
return typeof x == 'number';
}
function show(x: number | string): void {
if (isNumber(x)) {
console.log("number value: " + x);
} else {
console.log("string value: " + x);
}
}
show("hello");
show(4);
Outputstring value: hello number value: 4
instanceof type guards
TypeScript works well with JavaScript's instanceof operator as a type guard (without type assertions or user-defined type guard functions):
instance-of-guards-example.tsclass Car {
drive() {
console.log("car driving");
}
}
class Bike {
ride() {
console.log("Bike ridding");
}
}
function move(vehicle: Bike | Car): void {
if (vehicle instanceof Car) {
vehicle.drive();//no compile time or runtime errors
} else {
vehicle.ride();
}
}
move(new Bike());
OutputBike ridding
Example ProjectDependencies and Technologies Used: |