こんにちは!フロントエンドからバックエンドまで、TypeScriptで頭を悩ませつつも、その型システムの美しさに魅了されている開発者の皆さん。
今回は、TypeScriptの型システムが持つ「本当の魔術」とも言える領域──「Template Literal Types(テンプレートリテラル型)」と、それらを組み合わせた文字列の型安全化について、一緒に深く見ていきましょう。
他の言語からやってきた開発者の中には、「えっ、型の中で文字列の結合や操作ができるの?」と驚く方も多いはずです。ここをクリアすれば、あなたの書くコードの安全性と開発体験は文字通り一段階上のステージに引き上げられます。
難しい数式のような型定義に圧倒される必要はありません。優しく、でも本質的なところまでしっかりと噛み砕いて解説していくので、ぜひ最後までついてきてくださいね。
—
1. なぜ「ただの文字列型 (`string`)」では物足りないのか?
普段、私たちは何気なく `string` という型を使っていますよね。関数を作るときも、APIのパスを指定するときも、とりあえず `url: string` と書いておけばエラーは起きません。
// ただの string 型を使った例
function fetchApi(endpoint: string) {
// …
}
// うっかりタイポしても、コンパイラは何も言ってくれない…
fetchApi(“/api/v1/usre”); // “usre” って本当は “user” なのに!
このコード、動かしてみるまでタイポ(打ち間違い)に気づきません。もしこれが大規模なアプリケーションだったら、実行時エラーの温床になってしまいますよね。
「決まったパターンの文字列だけを受け付けたい」「APIのパスは必ず `/api/v1/` から始まってほしい」。
そんな願いを叶えてくれるのが、TypeScriptのテンプレートリテラル型なんです。
—
2. テンプレートリテラル型の基本:型で作る「文字列のパズル」
JavaScriptのテンプレート構文(バックティック \` \`)はご存知ですよね。TypeScriptのテンプレートリテラル型は、あの文字列補完の仕組みを、そのまま「型」の世界で行うものです。
まずは基本の形を見てみましょう。
type Action = “read” | “write”;
type Resource = “user” | “post”;
// 2つの型を組み合わせる(直積が自動で展開されます!)
type Permission = `${Action}_${Resource}`;
// コンパイル時に生成される型は、実質的に以下のようになります:
// type Permission = “read_user” | “read_post” | “write_user” | “write_post”;
これ、すごくないですか? `Action` と `Resource` を掛け合わせた4通りの文字列が、コンパイル時に自動生成されています。
ここで、実際にこの型を使う関数を書いてみましょう。
function checkPermission(perm: Permission) {
console.log(`Checking permission for: ${perm}`);
}
// 正しい組み合わせ
checkPermission(“read_user”); // OK!
// ❌ 存在しない組み合わせやタイポは、即座にコンパイルエラー!
// checkPermission(“delete_user”);
// ❌ Argument of type ‘”delete_user”‘ is not assignable to parameter of type ‘Permission’.
「ここをクリアすれば、TypeScriptの基本はバッチリマスターできますよ」と言ったのは、まさにこの感覚です。「あり得ない値」を型レベルで予め排除する。これがTypeScriptによる堅牢な設計の第一歩です。
—
3. 実践!APIエンドポイントの型安全化
それでは、もう少し実務に近い例を見てみましょう。
Web開発で避けて通れないのが「APIエンドポイントのパス指定」です。バージョンやリソース名がバラバラになりがちなURLを、型でガチガチに守ってみましょう。
// APIのバージョンの型
type ApiVersion = “v1” | “v2”;
// リソースの型
type ApiResource = “users” | “posts” | “comments”;
// テンプレートリテラル型でエンドポイントのルールを定義
type ApiEndpoint = `/api/${ApiVersion}/${ApiResource}`;
// 生成される型:
// “/api/v1/users” | “/api/v1/posts” | “/api/v1/comments” |
// “/api/v2/users” | “/api/v2/posts” | “/api/v2/comments”
これを使って、型安全なフェッチ関数を作ってみます。
async function fetchFromApi(endpoint: ApiEndpoint) {
const response = await fetch(endpoint);
return response.json();
}
// 使用例
fetchFromApi(“/api/v1/users”); // ✨ 完璧に補完が効き、エラーもなし!
// ❌ エラー: スラッシュの抜けやバージョンの間違いを検知
// fetchFromApi(“/api/v3/users”);
// fetchFromApi(“/apiv1/users”);
開発中にエディタの補完(IntelliSense)が `v1/users`, `v1/posts` などをズラッとサジェストしてくれるようになるため、ドキュメントを見に行かなくてもコードがスラスラ書けるようになります。これは開発体験として最高ですよ!
—
4. 応用:CSSのクラス名やイベント名を型で縛る
テンプレートリテラル型は、APIだけでなくUIコンポーネントのデザインシステムやイベントハンドリングでも真価を発揮します。
例えば、BEM(Block Element Modifier)のような規則的なCSSクラス名を型で表現してみましょう。
type ComponentName = “button” | “card”;
type SizeVariant = “sm” | “md” | “lg”;
// 「button-sm」「card-lg」のようなクラス名ルールを強制する
type ComponentClassName = `${ComponentName}-${SizeVariant}`;
const renderComponent = (className: ComponentClassName) => {
// …
};
renderComponent(“button-md”); // OK
// renderComponent(“button-xl”); // ❌ エラー (“xl” は存在しない)
また、UIライブラリなどでよくある「イベント名+ハンドラー名」の生成にも使えます。
type EventTypes = “click” | “hover” | “focus”;
// “onClick” | “onHover” | “onFocus” のようなReact風のプロパティ名を自動生成
type EventHandlerNames = `on${Capitalize
// ※ Capitalize
const handlers: Record
onClick: () => console.log(“clicked”),
onHover: () => console.log(“hovered”),
onFocus: () => console.log(“focused”),
};
`Capitalize` のような組み込み文字列操作型(`Uppercase`, `Lowercase`, `Uncapitalize` など)と組み合わせることで、TypeScriptの表現力はもはや「プログラミング言語のコンパイラの中でもう一つのミニ言語が動いている」かのような領域に到達します。
—
5. 陥りやすい文法エラーと注意点
ここで、初心者のうちによくハマりがちなポイントをいくつかシェアしておきますね。
① `string` 型とテンプレートリテラル型の混同
もしテンプレートリテラルのベースとなる型に、広すぎる `string` 型を混ぜてしまうと、型が崩壊することがあります。
type BadId = `${string}_id`; // これは広すぎて何でも受け入れてしまう
テンプレートリテラル型を使うときは、できる限りユニオン型(`”a” | “b”`)のような有限の確定した文字列をベースにするのが、型安全性を保つ鉄則です。
② コンパイルの負荷(型の肥大化)
あまりにも多くの文字列を掛け合わせすぎると(例:1000個の要素 × 1000個の要素など)、TypeScriptの型チェッカーが計算に時間がかるようになり、エディタの動作が重くなる原因(いわゆる「型爆発」)になります。
実用的な範囲(数十〜数百パターン程度)にとどめるのが、シニアな設計のコツです。
—
おわりに
今回は、テンプレートリテラル型とType Aliasを駆使した文字列の型安全化について解説しました。
- ただの `string` を卒業し、具体的なパターンを型として定義する
- `Action` や `Resource` などのパーツを組み合わせて自動展開する
- APIパスやコンポーネントの命名規則をコンパイル時に強制する
このテクニックが手に入ると、動かしてみなければ分からなかったバグをごっそりコンパイル時に駆逐できるようになります。何より、エディタの爆発的な補完機能に触れるたび、TypeScriptを書くのがもっと楽しくなるはずです。
ぜひ、皆さんの日々の開発や個人開発のコードでも試してみてくださいね。それでは、また次回のモダン・TypeScriptでお会いしましょう!