TypeScript Type Narrowing: `typeof`

In TypeScript, type narrowing is a powerful technique that allows you to refine the type of a variable within a certain scope. One of the most commonly used ways to perform type narrowing is by using the typeof operator. TypeScript’s typeof operator can be used to get the type of a variable at compile - time, and it can be leveraged to narrow down the possible types of a variable, enabling more precise type checking and better code safety. This blog will explore the fundamental concepts, usage methods, common practices, and best practices of using typeof for type narrowing in TypeScript.

Table of Contents

  1. Fundamental Concepts
  2. Usage Methods
  3. Common Practices
  4. Best Practices
  5. Conclusion
  6. References

Fundamental Concepts

What is Type Narrowing?

Type narrowing is the process of taking a wider type and making it more specific. For example, if you have a variable that can be either a string or a number, type narrowing can help you determine whether it is a string or a number at a certain point in the code, so that you can perform operations specific to that type.

The typeof Operator in TypeScript

In JavaScript, the typeof operator returns a string indicating the type of a value. In TypeScript, the typeof operator can also be used in type positions to refer to the type of a variable. When used in conditional statements, it can be used to narrow down the type of a variable.

Here are the possible return values of the typeof operator in JavaScript (and TypeScript):

  • "undefined"
  • "boolean"
  • "number"
  • "string"
  • "object"
  • "function"
  • "symbol"

Usage Methods

Using typeof in if Statements

The most common way to use typeof for type narrowing is in if statements. Consider the following example:

function printValue(value: string | number) {
    if (typeof value === 'string') {
        // Inside this block, TypeScript knows that 'value' is a string
        console.log(value.toUpperCase());
    } else {
        // Here, TypeScript knows that 'value' is a number
        console.log(value.toFixed(2));
    }
}

printValue('hello');
printValue(3.14159);

In this example, the typeof operator is used in the if statement to check if value is a string. If the condition is true, TypeScript narrows down the type of value to string inside the if block. Otherwise, it narrows the type to number in the else block.

Using typeof with switch Statements

You can also use typeof in switch statements for type narrowing. Here is an example:

function handleValue(value: string | number | boolean) {
    switch (typeof value) {
        case 'string':
            console.log(`The string is: ${value}`);
            break;
        case 'number':
            console.log(`The number is: ${value}`);
            break;
        case 'boolean':
            console.log(`The boolean is: ${value}`);
            break;
        default:
            console.log('Unexpected type');
    }
}

handleValue('test');
handleValue(42);
handleValue(true);

Common Practices

Narrowing for Function Parameters

When writing functions that accept union types as parameters, using typeof for type narrowing can make the function more robust. For example, a function that can accept either a string or an array of strings and returns the length:

function getLength(value: string | string[]) {
    if (typeof value === 'string') {
        return value.length;
    } else {
        return value.reduce((acc, str) => acc + str.length, 0);
    }
}

console.log(getLength('hello'));
console.log(getLength(['hello', 'world']));

Narrowing in Error Handling

Type narrowing can also be useful in error handling scenarios. For example, when a function can return either a result or an error object:

type Result = { success: true; data: string } | { success: false; error: string };

function processResult(result: Result) {
    if (typeof result.error === 'string') {
        console.error(`Error: ${result.error}`);
    } else {
        console.log(`Data: ${result.data}`);
    }
}

const successResult: Result = { success: true, data: 'Some data' };
const errorResult: Result = { success: false, error: 'Something went wrong' };

processResult(successResult);
processResult(errorResult);

Best Practices

Keep Conditions Simple

When using typeof for type narrowing, try to keep the conditions simple. Complex conditions can make the code harder to read and maintain. For example, avoid nested typeof checks if possible.

Use Early Returns

If you can determine the type of a variable early in a function and return from the function, it can make the code more readable. Consider the following example:

function processValue(value: string | number) {
    if (typeof value === 'string') {
        return value.toUpperCase();
    }
    return value.toFixed(2);
}

console.log(processValue('example'));
console.log(processValue(5.67));

Document Type Narrowing

When using typeof for type narrowing in more complex scenarios, it can be helpful to add comments to the code to explain the type narrowing logic. This makes the code more understandable for other developers.

Conclusion

TypeScript’s typeof operator is a valuable tool for type narrowing. It allows you to write more precise and safer code by refining the type of a variable within a certain scope. By using typeof in if statements, switch statements, and other conditional constructs, you can perform operations specific to the narrowed type. Following the common practices and best practices outlined in this blog can help you use typeof for type narrowing effectively.

References