TypeScript is a powerful language that adds static types to JavaScript, enabling developers to write safer and more maintainable code. One of the key features that enhance TypeScript’s robustness is type guards. Type guards allow us to narrow down the type of a variable within a conditional block, providing better type safety and reducing runtime errors. In this article, we will explore various custom type guards and demonstrate how they can be used to ensure safe type checks in TypeScript.
Understanding Type Guards
Type guards are functions or expressions that perform runtime checks to determine if a variable is of a specific type. When a type guard function returns true, TypeScript understands that the variable has the specific type within that scope.
Creating Custom Type Guards
Let's create custom type guards for common types and scenarios, demonstrating their usage with practical examples.
Checking for undefined
The isUndefined type guard checks if a value is undefined.
// isUndefined.ts
export function isUndefined(value: unknown): value is undefined {
return value === undefined;
}
var x: null | undefined;
if (isUndefined(x)) {
const y = x; // y: undefined
} else {
const z = x; // z: null
}
In this example, isUndefined helps us narrow down the type of x to undefined or null.
Checking for null
The isNull type guard checks if a value is null.
// isNull.ts
export function isNull(value: unknown): value is null {
return value === null;
}
var x: null | undefined;
if (isNull(x)) {
const y = x; // y: null
} else {
const z = x; // z: undefined
}
Here, isNull helps us determine whether x is null or undefined.
Checking for number
The isNumber type guard checks if a value is a number.
// isNumber.ts
import { getStringOrNumberRandomly } from "./helpers";
export function isNumber(value: unknown): value is number {
return typeof value === "number";
}
var data: string | number = getStringOrNumberRandomly();
if (isNumber(data)) {
const y = data; // y: number
} else {
const z = data; // z: string
}
In this case, isNumber ensures that data is either a number or a string.
Checking for string
The isString type guard checks if a value is a string.
// isString.ts
import { getStringOrNumberRandomly } from "./helpers";
export function isString(value: unknown): value is string {
return typeof value === "string";
}
var data: string | number = getStringOrNumberRandomly();
if (isString(data)) {
const y = data; // y: string
} else {
const z = data; // z: number
}
Here, isString confirms that data is either a string or a number.
Checking for a Complex Type
The isPerson type guard checks if an object conforms to the Person interface. This type guard leverages the previously created type guards (isString, isNumber, isNull) to validate the structure of a more complex object.
// isPerson.ts
import { isNull } from "./isNull";
import { isNumber } from "./isNumber";
import { isString } from "./isString";
type Person = {
name: string,
age: number,
email: string | null
}
function isPerson(rawValue: unknown): rawValue is Person {
const value = rawValue as Person;
return (
isString(value.name) &&
isNumber(value.age) &&
(isString(value.email) || isNull(value.email))
);
}
const person: any = {
name: 'Harry',
age: 17,
email: null
}
if (isPerson(person)) {
const y = person; // y: Person
} else {
const z = person; // z: any
}
The isPerson type guard validates whether an object meets the structure of the Person type. By using the isString, isNumber, and isNull type guards, isPerson checks if name is a string, age is a number, and email is either a string or null. This composite type guard ensures that all properties of the Person object are correctly typed.
In this example, isPerson helps us confirm that an object adheres to the expected Person interface, allowing TypeScript to understand the exact structure and types of the object's properties within the conditional block.
Conclusion
Type guards in TypeScript provide a powerful way to ensure type safety and improve code reliability. By creating custom type guards, you can handle complex type checks efficiently and avoid common pitfalls associated with dynamic types. Leveraging these techniques will help you write cleaner, more maintainable code, ultimately leading to more robust applications.
Top comments (0)