Happylab Logo
Published on

Day17: Learning TypeScript: Narrowing

Authors

Day17: Learning TypeScript: Narrowing

Actually, when I was reading this article on the official website, I really wanted to pass it; I found it a bit hard to read XD, but I still took notes. If there are any mistakes, please feel free to leave comments. Thank you!

Narrowing

Narrowing refers to the process of reducing a variable that could be of multiple types to a specific type, usually used in union types. The benefit is that it acts like checking what type you are dealing with and doing the corresponding action, which is clear in its division of labor, often appearing in if... else statements.

It's a bit abstract, so let's understand it with code:

❌ According to the following padLeft function, the intended functionality is that when the padding parameter is a number, it will add that many spaces in front of the input. If the padding is a string, it will prepend that string to the input. However, in the following example, when the padding parameter is passed a number, it will throw an error.

function padLeft(padding: number | string, input: string) {
  return new Array(padding + 1).join(" ") + input;
}

//error: Operator '+' cannot be applied to types 'string | number' and 'number'.

✅ Therefore, we should first check if the padding type is a number, and then handle each type accordingly. The following example uses typeof to narrow the execution scope of different types, and this process is called narrowing. In fact, I have used related methods in previous articles, which made me realize that there is a concept of narrowing in TypeScript.

function padLeft(padding: number | string, input: string) {
  if (typeof padding === "number") { // number
    return new Array(padding + 1).join(" ") + input;
  }
  return padding + input; // otherwise, it's a string
}

Using typeof Type Guards

The typeof operator allows us to obtain the type of a value, such as string, number, boolean, symbol, undefined, object, and function, but not null.

In the following example, typeof strs === "object" is a type guard. Although null cannot be used with typeof to obtain its type, null is also an object, and TypeScript will remind us that strs could possibly be null. (Remember to enable strict mode in tsconfig.)

function printAll(strs: string | string[] | null) {
    if (typeof strs === "object") {
      for (const s of strs) {
       // Object is possibly 'null'.
      }
    } else if (typeof strs === "string") {
      console.log(strs);
    } else {
      console.log(strs);
    }
}

Equality Narrowing

You can use switch, ===, !==, ==, and != to narrow types.

function printAll(strs: string | string[] | null) {
  if (strs !== null) {
    if (typeof strs === "object") {
      for (const s of strs) {
        console.log(s);
      }
    } else if (typeof strs === "string") {
      console.log(strs);
    }
  }
}

Using "value" in x

Using the concept of "value" in x, where value is a string literal and x is a union type (animal).

type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
  if ("swim" in animal) {  // if the animal object has the property "swim", it must be of type Fish
    return animal.swim();
  }

  return animal.fly();
}

Using instanceof

instanceof is used to check whether a value is an instance of a certain constructor. In the following example, if the x parameter is of the Date constructor, it can use the toUTCString() method. Otherwise, it is a string, and the toUpperCase() method is used.

function logValue(x: Date | string) {
  if (x instanceof Date) {
    console.log(x.toUTCString());
    //(parameter) x: Date
  } else {
    console.log(x.toUpperCase());
    //(parameter) x: string
  }
}

Using Assignments

When we assign a value, we also perform narrowing. In the example below, when assigning a value to x, its type will be number or string. Assigning either a number or a string is okay, but assigning a boolean will throw an error.

let x = Math.random() < 0.5 ? 10 : "hello world!";
console.log(Math.random());
console.log(x);
x = 1;
console.log(x); // 1
x = true;  // error: Type 'boolean' is not assignable to type 'string | number'.


In the next article, I will continue discussing narrowing. I find it difficult, at least I think so QQ. See you tomorrow!


References

https://www.typescriptlang.org/docs/handbook/2/narrowing.html