pwshub.com

What are Type Predicates in TypeScript? Explained with Code examples

What are Type Predicates in TypeScript? Explained with Code examples

Type predicates are an interesting syntactical feature in TypeScript. While they appear in the same place as return type annotations, they look more like short affirmative sentences than typical annotations. This gives you greater control over type checking.

With the release of TypeScript 5.5, working with type predicates has become more intuitive now because it can infer them automatically in many cases. But if you're navigating slightly older code-bases, you're likely to encounter handwritten type predicates more often.

In this article, we will briefly explore what type predicates are and why they are useful. Let's start by looking at the problem they solve.

The Problem

The best way to understand the usefulness of type predicates, I believe, is by noticing the problems that arise when we don't have them:

function isString(value: unknown): boolean {
  return typeof value === "string";
}
function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string | number
  }
  return " ".repeat(padding) + input; // Opps type error here
                 //   ^
                 // string | number
}

Here, the return type of isString is set to boolean, and we use it in a function called padLeft to add padding to the left of an input string. The padding can be either a given string or a specified number of space characters.

You might be wondering why I hard-coded the return type to boolean. This is to illustrate the problem. If you don't add any return type annotation and use the latest version of TypeScript, you won't notice any issue here. For now, bear with me – we'll discuss the version-related differences shortly.

The function will work smoothly at runtime, but TypeScript cannot perform any type narrowing with isString. As a result, the type of padding remains string | number both inside and outside the if statement. This leads to a conflict with repeat's expectation for its first argument, causing the type error.

The Solution: Enter Type Predicates

Even if you are unfamiliar with the term predicate, you have likely used them before. Predicates in programming are simply functions that return a boolean to answer a yes/no question. Several JavaScript built-in array methods, such as filter, find, every, and some, use predicates to help with decision-making.

Type predicates are a way to make predicates more useful for type narrowing. We can fix the problem by using a type predicate as the return type:

function isString(value: unknown): value is string {
  return typeof value === "string";
}

Here the type predicate is value is string. It is saying three things:

  • The function is a predicate. So TypeScript will show an error if you try to return anything other than a Boolean value.

  • If it returns true, then value is of type string.

  • If it returns false, then value is not of type string.

Type predicates let you create user-defined type guards. Type guards are logical checks that let you refine types to more specific types, aka narrow them. So, the above function is also a user-defined type guard.

Here is the full code:

function isString(value: unknown): value is string {
  return typeof value === "string";
}
function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string
  }
  return " ".repeat(padding) + input;
                 //   ^
                 // number
}

Here, TypeScript correctly narrows the type of padding inside the if statement and outside of it.

Now let's briefly look at how type predicates worked before TypeScript 5.5 and what this version has improved.

Type Predicates Before TypeScript 5.5

In our previous example, if we don't specify any return type, it will be inferred as boolean:

function isString(value: unknown) {
  return typeof value === "string";
}
function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string | number
  }
  return " ".repeat(padding) + input; // Opps type error here
                 //   ^
                 // string | number
}

As a result, we have the same error as when we manually wrote the return type boolean. Here is the TypeScript playground link for the above code fragment. Go and hover of the functions or variables for a better feeling of the types. Then see how writing the type predicate solves the problem.

If we don't specify the type predicate, using methods like filter can also result in incorrect type detection:

function isString(value: unknown) {
  return typeof value === "string";
}
const numsOrStrings = [1, 'hello', 2, 'world'];
//      ^
//    strings: (string | number)[]
const strings = numsOrStrings.filter(isString);
//      ^
//    strings: (string | number)[]

Now let's see how TypeScript 5.5 improves the situation.

Type Predicates After TypeScript 5.5

One of the top features of TypeScript 5.5 is it can infer type predicates by analyzing the function body. So if you are using TypeScript 5.5 or later, you don't have to write the type predicate as the return type of isString. TypeScript does it for you, and code like what you see in the example below works perfectly fine:

function isString(value: unknown) {
  return typeof value === "string";
}
function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string
  }
  return " ".repeat(padding) + input; // Opps type error here
                 //   ^
                 // number
}
const numsOrStrings = [1, 'hello', 2, 'world'];
const strings = numsOrStrings.filter(isString);
//      ^
//    strings: string[]
const numbers = numsOrStrings.filter((v) => !isString(v));
//      ^
//    numbers: number[]

I haven't yet found a situation where I'm unhappy with the automatic inference of type predicates. If you do find one, you can always write your own manually.

Further Study

In this article, we briefly explored type predicates in TypeScript. If you're interested in learning more and understanding the edge cases, here are the official guides:

Thanks for reading! See you next time!

Cover photo background is from Mona Eendra on Unsplash

Source: freecodecamp.org

Related stories
1 month ago - This tutorial will guide you through the core concepts and new features of Java Streams, covering basic and advanced stream operations.
1 month ago - In this tutorial, you'll learn how to use Python's rich set of operators and functions for working with strings. You'll cover the basics of creating strings using literals and the str() function, applying string methods, using operators...
1 week ago - This tutorial teaches you how to use the where() function to select elements from your NumPy arrays based on a condition. You'll learn how to perform various operations on those elements and even replace them with elements from a separate...
1 week ago - The rapid evolution of artificial intelligence (AI) has resulted in a powerful synergy between large language models (LLMs) and AI agents. This dynamic interplay is sort of like the tale of David and Goliath (without the fighting), where...
1 month ago - Predictive analytics leverages statistical algorithms, machine learning models, and historical data to identify patterns and forecast future insights and trends. Businesses can use these insights to optimize operations and enhance...
Other stories
15 minutes ago - Learn how to set up an Amazon API Gateway and secure the REST API with Okta Customer Identity Cloud (CIC) Adaptive MFA Risk Score
1 hour ago - This release candidate, a near-final look at Deno 2, includes the addition of Node's process global, better dependency management, and various API stabilizations, and more.
1 hour ago - Published: September 19, 2024 The CSS Working Group has combined the two CSS masonry proposals into one draft specification. The group hopes that...
1 hour ago - Stay organized with collections Save and categorize content based on your preferences. Published: September...
3 hours ago - DNS monitoring tool is a cloud-based scanner that constantly monitors DNS records and servers for anomalies. This tool aims to ensure that users are sent to genuine and intended website pages, instead of fakes or replicas. It alerts users...