hrtyy.dev

Understanding Intersection Types in TypeScript

A deep dive into intersection types in TypeScript, exploring how they work with primitives and objects.

Introduction

In this blog post, we will explore intersection types in TypeScript, particularly focusing on how they behave when combining primitives and objects.

This technique is applied in branded types, which are a powerful way to create more specific and constrained types.

Intersection number & object

Intersection types allow you to combine multiple types into one. This means that a value of an intersection type must satisfy all the combined types.

1type A = number & object
2type B = number & {
3 hoge: "hoge"
4}

Let's look at the above example.

In this case, A is never because a value cannot be both a number and an object at the same time. However, B is not never.

But why?

JavaScript Primitives

We often use methods on primitives. For example,

1let num = 1
2console.log(num.toFixed()) // 1
3
4let s = "abcde"
5console.log(s.includes("c")) // true

In fact, we are not calling methods on primitives, but JavaScript auto-boxes the value into a wrapper object when properties are accessed.

MDN says

> Primitives are immutable and do not have methods. However, they behave as if they do because JavaScript auto-boxes the value into a wrapper object when properties are accessed.

Let's check this behavior.

1let num = 1
2console.log(num.toFixed()) // 1
3
4Number.prototype.toFixed = () => "hello"
5console.log(num.toFixed()) // "hello"

The first console.log prints 1. By overriding Number.prototype.toFixed, we change the implementation of the method for any auto-boxed Number.

As a result, num.toFixed() no longer returns 1, but instead returns "hello". This illustrates how the underlying mechanism of auto-boxing.

TypeScript Primitive Types

Primitives are not objects, but their properties can be accessed. TypeScript type must handle this behavior.

  • object type is absolutely an object, so the intersection of a number and an object becomes never.

  • { hoge: "hoge" } type looks like an object, but I think it just says "this type has hoge property".

We also can get the key of a number type using keyof keyword.

1type NumberKey = keyof number

The difference between type A and type B sounds natural when we consider the behavior of primitives in JavaScript.