Introduction to TypeScript Pattern Matching with ts-pattern: A Beginner's Guide
If you're working with TypeScript, you probably appreciate the type-safety and structure it brings to JavaScript. However, when it comes to writing complex logic, like handling different data structures, it's easy to fall into the trap of long if-else
chains or switch
statements that can quickly become messy and hard to maintain. This is where pattern matching can help you write more concise, readable, and type-safe code.
ts-pattern
is a lightweight TypeScript library that introduces pattern matching to your codebase. It allows you to define patterns for different structures and take actions based on those patterns. This means cleaner logic, better readability, and fewer bugs.
What is Pattern Matching?
At its core, pattern matching is a way to check a given value against specific patterns and then branch your logic based on which pattern matches. It’s common in functional programming languages like Haskell or Scala, but TypeScript traditionally relies on if-else
or switch-case
to handle different types of data. With ts-pattern
, you get an intuitive, type-safe way to match your data against patterns in a more declarative style.
Imagine you’re dealing with shapes in a program: circles, squares, and triangles. Instead of having a big if
block that checks for the type of shape and then calculates the area, you can use ts-pattern
to simplify this task.
Why Use ts-pattern
?
The match
function in ts-pattern
is the heart of the library. It allows you to take a value, define patterns that describe its possible shapes or states, and match the value to one of those patterns.
Here’s an example:
import { match } from 'ts-pattern';
type Shape =
| { type: 'circle'; radius: number }
| { type: 'square'; side: number };
const shape: Shape = { type: 'circle', radius: 10 };
const area = match(shape)
.with({ type: 'circle' }, (shape) => Math.PI * shape.radius ** 2)
.with({ type: 'square' }, (shape) => shape.side ** 2)
.exhaustive();
console.log(area); // Output: 314.159...
In this example, instead of manually checking the type of the shape, the match
function does it for us. The patterns we define using .with
match the structure of the data, and the corresponding function is run when the pattern is matched. This results in more readable code that is also type-safe.
Key Concepts
1. Type Safety
One of the best features of ts-pattern
is that it's fully type-safe. Since it’s built for TypeScript, it takes advantage of TypeScript's types and will throw an error if you forget to handle one of the possible cases. This ensures your code is robust and reduces runtime errors.
For example, if you add a new shape, say a triangle, TypeScript will let you know that you're missing this case in your pattern matching, thanks to the .exhaustive()
method. This feature ensures all possible cases are accounted for.
.match({ type: 'triangle' }, () => { /* handle triangle */ })
2. Exhaustiveness Checks
The .exhaustive()
method forces you to handle every possible pattern. If you forget to match one, the TypeScript compiler will give you an error. This is incredibly useful when working with complex data models or when new cases are added, as it prevents bugs from unhandled patterns.
3. Declarative Code
One thing you'll notice when using ts-pattern
is how declarative your code becomes. You're essentially telling the program what to do in different scenarios, rather than writing procedural logic step by step. This makes the intent of the code clearer, which is great for maintainability and readability.
More Than Just Type Matching
While the above example shows type matching, ts-pattern
is capable of much more. You can match nested objects, arrays, and even specific values. Let’s look at a more advanced example:
const data = { status: 'success', value: 42 };
const result = match(data)
.with({ status: 'success', value: 42 }, () => 'Special Success!')
.with({ status: 'success' }, () => 'Generic Success')
.with({ status: 'error' }, () => 'Error')
.exhaustive();
console.log(result); // Output: "Special Success!"
In this case, we’re matching not just on the status
field but also on the value
. The flexibility to match specific values within objects makes ts-pattern
a great tool for handling any kind of structured data.
Handling Nested Objects
Sometimes, you’re working with more complex, nested data structures. Pattern matching can dive into these structures and match based on deeply nested fields.
const user = { profile: { name: 'John', age: 30 }, active: true };
const isActive = match(user)
.with({ profile: { name: 'John' }, active: true }, () => 'John is active')
.with({ active: false }, () => 'User is inactive')
.exhaustive();
console.log(isActive); // Output: "John is active"
This flexibility allows you to write clean, readable code without resorting to nested if
statements.
Matching Arrays and Tuples
Another feature that makes ts-pattern
powerful is the ability to match arrays or tuples. This is particularly useful when dealing with sequences of data or functions that return multiple values:
const point = [1, 2] as const;
const quadrant = match(point)
.with([x => x > 0, y => y > 0], () => 'First quadrant')
.with([x => x < 0, y => y > 0], () => 'Second quadrant')
.with([x => x < 0, y => y < 0], () => 'Third quadrant')
.with([x => x > 0, y => y < 0], () => 'Fourth quadrant')
.exhaustive();
console.log(quadrant); // Output: "First quadrant"
In this example, we’re matching tuples using conditions, making ts-pattern an elegant solution for working with structured data.
Summary: Why ts-pattern
?
If you’re still using if-else
chains or switch
statements in your TypeScript code, it might be time to consider adopting ts-pattern
. Here are some of the main reasons to use it:
- Cleaner code: By separating the patterns from the logic, you end up with code that’s much more readable and maintainable.
- Type-safety: You can confidently handle complex data knowing that TypeScript is keeping an eye on your patterns.
- Exhaustiveness: No more missing cases or forgotten conditions.
- Versatility: Whether you’re dealing with simple objects, arrays, or deeply nested data,
ts-pattern
has you covered.
For beginners looking to take their TypeScript skills to the next level, ts-pattern is a great tool to explore. It simplifies code, improves readability, and makes maintaining your application easier over time. So, if you’re looking for a way to write better logic with TypeScript, start using ts-pattern
today!