Mastering Type Guards in TypeScript: Ensuring Safe Type Checks

geraldhamiltonwicks

Gerald Hamilton Wicks

Posted on June 14, 2024

Mastering Type Guards in TypeScript: Ensuring Safe Type Checks

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
geraldhamiltonwicks
Gerald Hamilton Wicks

Posted on June 14, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related