Tech
Tips
TypeScript
【ts-pattern】TypeScript にパターンマッチングを導入するメリット
2025年10月13日
2025年10月13日

こんにちは、けいこんぐらです!
今回は ts-pattern というライブラリを紹介します。
ts-pattern は TypeScript でパターンマッチングを実現するためのライブラリで、複雑な条件分岐を簡潔に表現できる強力な手法です。
この手法を用いることで、より安全で読みやすいコードを書くための大きな助けとなります 👌
興味がある方はぜひ見てみてください!
ts-pattern とは?
改めてですが、公式でもこのように記載があります 👀
より適切で安全な条件式を記述しましょう。パターンマッチングにより、複雑な条件を単一の簡潔な式で表現できます。コードが短くなり、読みやすくなります。網羅性チェックにより、考えられるケースをすべて網羅的にチェックできます。
なかなか良さそうですね!
ちなみに、パターンマッチングというのは、関数型プログラミング言語でよく使われる手法で、データの構造に基づいて処理を分岐させる方法で、Python、Rust、Swift、Haskell などで広く使われています。
これを TypeScript で実現しようというのが ts-pattern です 🔎
パターンマッチングのメリット
まずは、従来の Switch 文での条件分岐を考えてみましょう。
ステータスを判断し、それに応じたメッセージを返す関数を考えます。
type FetchState = { status: "loading" } | { status: "success"; data: string } | { status: "error" };
function getState(fetchState: FetchState) {
switch (fetchState.status) {
case "loading":
return "ローディング中...";
case "success":
return `${fetchState.data}`;
case "error":
return "エラーが発生しました";
}
}
普通の Switch 文ですがなんだか見た目も冗長で、型安全性もあまり感じられない気がします 🤔
ここで試しに other
という状態を追加してみましょう。
type FetchState =
| { status: "loading" }
| { status: "success"; data: string }
| { status: "error" }
| { status: "other" };
function getState(fetchState: FetchState) {
switch (fetchState.status) {
case "loading":
return "ローディング中...";
case "success":
return `${fetchState.data}`;
case "error":
return "エラーが発生しました";
}
}
// 'other' ケースが追加されたのに、Switch 文での網羅性チェックがされない、、
上記のように、other
のケースが追加されたのに、Switch 文での網羅性チェックがされていません。
書き手のミスで、other
のケースを忘れてしまう可能性があります 🙅♂️
一応、上記を回避する方法もあるにはあります!
type FetchState =
| { status: "loading" }
| { status: "success"; data: string }
| { status: "error" }
| { status: "other" };
function safeGuard(arg: never) {}
function getState(fetchState: FetchState) {
switch (fetchState.status) {
case "loading":
return "ローディング中...";
case "success":
return `${fetchState.data}`;
case "error":
return "エラーが発生しました";
default:
safeGuard(fetchState);
// Error:
// 型 '{ status: "other"; }' の引数を型 'never' のパラメーターに割り当てることはできません。ts(2345)
}
}
getState({ status: "other" });
// Output: undefined
しかし、上記のように冗長なコードを書く必要があり、結局、実行時にエラーになってくれません 💦
では、ts-pattern と使うとどうなるでしょうか?
import { match } from "ts-pattern";
type FetchState =
| { status: "loading" }
| { status: "success"; data: string }
| { status: "error" }
| { status: "other" };
function getState(fetchState: FetchState) {
return match(fetchState)
.with({ status: "loading" }, () => "ローディング中...")
.with({ status: "success" }, (state) => `${state.data}`)
.with({ status: "error" }, () => "エラーが発生しました")
.exhaustive();
// Error:
// この式は呼び出し可能ではありません。
// 型 'NonExhaustiveError<{ status: "other"; }>' には呼び出しシグネチャがありません。ts(2349)
}
getState({ status: "other" });
// Output:
// throw new NonExhaustiveError(input);
// e [Error]: Pattern matching error: no pattern matches value "other"
上記のように、コンパイル時に網羅性チェックがされ、実行時にも例外が発生してくれます!
ここで変わったのは以下です、いい感じですね ✨
- 実行時の安全性が上がった
- 条件分岐が文から式になった
- 可読性が上がった
嬉しいポイント
ここでは個人的に嬉しいポイントを紹介します。
1. より複雑なケースにも対応できる
これは「2(形) × 2(色)」の簡単な例ですが、以下のように複数の条件を組み合わせたパターンマッチングも可能です。
Switch 文の場合は、ネストさせる必要があるので、可読性はかなり上がります 👏
import { match } from "ts-pattern";
type Shape = { type: "circle" } | { type: "square" };
type Color = "red" | "blue";
type ColoredShape = { shape: Shape; color: Color };
// 1. ts-pattern でパターンマッチングをする場合
const getColoredShape1 = (coloredShape: ColoredShape): string => {
return match(coloredShape)
.with({ shape: { type: "circle" }, color: "red" }, () => "Red Circle")
.with({ shape: { type: "circle" }, color: "blue" }, () => "Blue Circle")
.with({ shape: { type: "square" }, color: "red" }, () => "Red Square")
.with({ shape: { type: "square" }, color: "blue" }, () => "Blue Square")
.exhaustive();
};
// 2. switch文で同じことをする場合
const getColoredShape2 = (coloredShape: ColoredShape): string => {
const { shape, color } = coloredShape;
switch (shape.type) {
case "circle":
switch (color) {
case "red":
return "Red Circle";
case "blue":
return "Blue Circle";
}
break;
case "square":
switch (color) {
case "red":
return "Red Square";
case "blue":
return "Blue Square";
}
}
};
2. let を const にできる
ts-pattern の場合、式として評価されるので、let
を使う必要がなくなり、const
にできます。
これはめちゃくちゃシンプルな例ですが、以下のように書けます ✍️
import { match } from "ts-pattern";
const NUMBER = 5;
// 式なのでそのまま const で宣言できる
const isOdd = match((NUMBER % 2) as 0 | 1)
.with(0, () => false)
.with(1, () => true)
.exhaustive();
// 文なので条件によって値を変更したい場合は、再代入するしかない
let isEven = false;
if (NUMBER % 2 === 0) {
isEven = true;
}
まだまだ他にも、ts-pattern にはたくさんの機能が備わっているので、興味がある方はぜひドキュメントを覗いてみてください 💻
🎨 The exhaustive Pattern Matching library for TypeScript, with smart type inference. - gvergnaud/ts
https://github.com
デメリット
ts-pattern は内部的に型レベルの計算に依存しており、プロジェクトの型チェックが遅くなる可能性があるそうです 🐢
Switch 文を使っている方が、パフォーマンス的には良いかもしれません。
ただ、型安全性と保守性という面で考えると、個人的には ts-pattern を使う方が良いと思っていますが、ここはトレードオフの部分なので、プロジェクトの規模や要件に応じて選択するといいと思います!
まとめ
ts-pattern は TypeScript にパターンマッチングを導入するための強力なライブラリで、複雑な条件分岐を簡潔に表現でき、型安全性と可読性を向上させます ✅
個人的には、メリットとデメリットを考慮した上で、ts-pattern を積極的に採用していきたいと思っています。
何よりも、ネストが深くならずに済むケースが増え、可読性が上がるのが嬉しいです!
TypeScript の基本的な設計には組み込まれていないパターンマッチングなので、いろんな意見があるかと思いますが、興味がある方はぜひ試してみてください 👍
参考
In the past few years, frontend development has become increasingly declarative. React shifted our..
https://dev.to
