Everything you need to know about Typescript Type Guards
Nikolas ⚡️
Posted on February 12, 2023
Type guards can be used to narrow down the type of any value in TypeScript. They can be used to check the type of variables, function return values, or elements in arrays, for example.
In general, type guards can be used whenever you have a value that could be of multiple types and you need to determine its actual type in order to perform specific operations on it. The type guard allows you to tell the TypeScript compiler more information about the type of a value, allowing it to provide better type checking and autocompletion in your code.
There are several built-in type guards available in TypeScript:
-
typeof
type guard: checks if a value is of a certain primitive type (e.g.number
,string
,boolean
), -
in
type guard: checks if a value is a property of an object or one of the properties of an union type, -
instanceof
type guard: checks if a value is an instance of a particular class or interface, -
is
type guard: a custom type guard that can be created by checking the shape of an object, -
never
type guard: represents values that never occur.
typeof
The typeof
operator is used to check the type of a value in JavaScript. It returns a string representing the type of the operand.
function add1000(input: string | number): number {
if (typeof input === 'number') {
return input + 1000;
} else {
return parseInt(input, 10) + 1000;
}
}
In this example, typeof input === ‘number’
checks if the type of input is number
. If it is, the type of input within the if block will be narrowed to number
, and the input + 1000
expression is valid because both operands have the same type.
typeof
type guards can also be used with string literals, union types, and type aliases.
It's crucial to remember that typeof
type guards are only effective when used with string, number, boolean, and symbol types. For more complex data types, you need to rely on in
or instanceof
type guards. The typeof type guard can identify the following data types: undefined
returns "undefined"
, null
returns "object"
, boolean
returns "boolean"
, number
returns "number"
, BigInt
returns "bigint"
, string
returns "string"
, symbol
returns "symbol"
, functions (including classes) return "function"
, and any other object returns "object"
. However, for non-primitive data types, typeof
type guard is less effective and instanceof
or in
type guards are preferred.
in
It basically solves the problem with typeof
and objects. Checks if a property or method is available on an object and narrows the type of the object based on the result. For example:
interface Dog {
move: () => void,
bark: () => string,
}
interface Person {
move: () => void,
talk: () => string,
}
function makeNoise(dogOrPerson: Dog | Person): void {
if ('bark' in dogOrPerson) {
dogOrPerson.bark() // here only bark and move methods will be shown
} else {
dogOrPerson.talk() // here only methods specific for Person will be available
}
}
instanceof
The instanceof
type guard is used to check if an object is an instance of a particular class or constructor function. The instanceof
operator is used to check if an object is an instance of a given constructor function. If the object is an instance of the constructor function, the type of the object is narrowed to be the type of the constructor function.
class Car {
wheels: number;
constructor(wheels: number) {
this.wheels = wheels;
}
}
class SportsCar extends Car {
doors: number;
constructor(wheels: number, doors: number) {
super(wheels);
this.doors = doors;
}
}
let vehicle: Car | SportsCar = new SportsCar(4, 2);
if (vehicle instanceof SportsCar) {
console.log(`The vehicle has ${vehicle.doors} doors.`);
}
If the condition vehicle instanceof
SportsCar
is true
, then the type of the vehicle
variable is narrowed down from Car | SportsCar
to SportsCar
. This means that the TypeScript type checker now knows that the vehicle variable is of type SportsCar
and allows you to access its properties and methods, such as doors
, without any type errors.
The instanceof
operator works by checking the prototype chain of an object. It returns true
if the constructor of the prototype of the object is equal to the constructor passed as the argument. In this case, it returns true
if the constructor of the prototype of the vehicle object is equal to the SportsCar
constructor.
By using the instanceof
type guard, you can improve the type safety of your code and avoid runtime errors. For example, if you tried to access the doors
property of the vehicle object without checking its type first, you would get a runtime error if the vehicle
object is actually an instance of the Car
class, as it does not have a doors
property. However, with the type guard in place, the TypeScript compiler will catch this error and prevent it from happening.
Another simple example checking if a value is an instance of a constructor function using Javascript Date
:
const value = new Date();
if (value instanceof Date) {
console.log(value.getFullYear());
}
Type predicates - is
A type predicate is a special type of type guard that allows you to narrow down the type of a value based on some condition.
import axios, { AxiosError } from 'axios';
const isAxiosError = (error: unknown): error is AxiosError => {
if (error && typeof error === 'object') {
return 'isAxiosError' in error;
}
return false;
};
const fetchData = async () => {
try {
await axios.get('http://examole.com/api');
} catch (error: unknown) {
if (isAxiosError(error)) {
console.error(`Failed with HTTP status "${error.status}`);
}
}
};
Remember that you can throw any value, including a string
or a number
, so we want to make sure that we use properties that are present in our error
object.
In this case, the type predicate is checking if the value of error
is of type AxiosError
by using the in
operator to check if the isAxiosError
property is present in the error object.
The is
type guard is a way to specify that the type of the value being checked is the type specified after is
. For example, error is AxiosError
. If the type predicate isAxiosError
returns true
, the type of error
is narrowed down to AxiosError
within the if
block, allowing you to access the properties of an AxiosError
object such as status.
Without this type predicate, you would have to either throw an error or cast the value to AxiosError
, both of which can be error-prone. Throwing an error would add overhead and complexity to the code, and casting the value to AxiosError
would ignore the possibility that the error is not of type AxiosError
.
By using the is
type guard, you can ensure that the code inside the if
block only runs if the error is indeed an AxiosError
, avoiding the possibility of an error being thrown or any other type being used.
never
The never
type in represents values that never occur. It is a bottom type, which means that it is a subtype of all other types.
A never
type guard is a way to narrow down the type of an object to the never
type. This is useful in cases where you want to express that a certain piece of code should never be reached, or that a certain function should never return a value.
A common use case of this type is checking all available cases with switch
statement:
enum ResponseType {
Success = "Success",
Failure = "Failure",
Error = "Error",
}
function handleResponse(response: ResponseType): string {
switch (response) {
case ResponseType.Success:
return "The operation was successful.";
case ResponseType.Failure:
return "The operation failed.";
case ResponseType.Error:
return "An error occurred."
default:
const neverValue: never = response;
throw new Error(`Unexpected response type: ${neverValue}`);
}
}
The type of the variable neverValue
is declared as never
, which represents values that never occur. This is used to express that the default case should never be reached, as all possible values of the response parameter are covered by the switch
statement.
By using the never
type in this way, you can enforce type safety and prevent bugs. If a new value is added to the ResponseTypee
enum in the future, and the switch statement is not updated to handle it, the default case will be triggered, and an error will be thrown with a message indicating the unexpected response type. This can help catch bugs early and improve the overall quality of your code.
It’s worth noting that in this example, the never
type is not used as a type guard. Instead, it is used to explicitly declare the type of a value that should never occur. The purpose of this is to indicate to the TypeScript compiler that the default case should never be reached, and to provide a more descriptive error message in case it is reached.
Conclusion
There are several built-in type guards available in TypeScript, including typeof
, in
, instanceof
, is
, and never
. The typeof
type guard is used to check the type of a value and can only be used with primitive types. The in
type guard is used to check if a property is available on an object and narrows the type of the object based on the result. The instanceof
type guard is used to check if an object is an instance of a particular class or constructor function, and narrows the type of the object if it is. By using type guards, you can improve the type safety of their code and avoid runtime errors.
⚡️ Action Points
- see my blog a about Javascript and React
- give a like or write a comment
Posted on February 12, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
August 31, 2024