やあ、現場の第一線でコードを書き続けている同志諸君。今日もTypeScriptの型パズルに頭を悩ませているだろうか?
「型は単なるガードレールだ」なんて甘い考えは、かつてのJavaScript時代の遺物だ。今のTypeScript、特に中級者以上のフロントエンドエンジニアに求められるのは、「型をいかにしてビジネスロジックやAPIの規約と同期させ、開発体験(DX)を極限まで高めるか」という視点だ。
今回は、意外と「なんとなく」で使われがちな、しかし使いこなすと劇的にコードの堅牢性が増す「文字列操作のための組み込みユーティリティ型」に焦点を当てよう。`Uppercase`, `Lowercase`, `Capitalize`, `Uncapitalize`。これらがテンプレートリテラル型と組み合わさった時、君のコードはただのプログラムから「芸術的な設計」へと昇華する。
準備はいいか? 現場の泥臭い知見を交えて、本質を解き明かしていこう。
—
1. なぜ「型で文字列を操作する」必要があるのか?
実務でよくあるシーンを思い出してほしい。バックエンドから送られてくる蛇足な `SNAKE_CASE` の定数、CSS-in-JSで扱う `kebab-case` のプロパティ名、あるいはイベントハンドラの `on` プレフィックス……。
これらを愚直に `string` 型として扱っていると、タイポ一つでバグが混入し、実行時エラーを見るまで気づけない。
TypeScript 4.1で登場したテンプレートリテラル型と、今回紹介する組み込み型は、こうした「文字列の命名規則」そのものを型定義として強制するために存在する。
組み込みの4騎士
まずは基本の4つをおさらいしよう。これらはTypeScriptのコンパイラ内部に組み込まれた(Intrinsic)特殊な型だ。
type Status = “active” | “inactive” | “pending”;
// 1. すべて大文字へ
type UpperStatus = Uppercase
// -> “ACTIVE” | “INACTIVE” | “PENDING”
// 2. すべて小文字へ
type LowerStatus = Lowercase<"ADMIN" | "USER">;
// -> “admin” | “user”
// 3. 先頭だけ大文字へ(キャメルケースからパスカルケースへの変換などで重宝)
type CapitalizedStatus = Capitalize
// -> “Active” | “Inactive” | “Pending”
// 4. 先頭だけ小文字へ
type UncapitalizedStatus = Uncapitalize<"UserAccount">;
// -> “userAccount”
2. 現場で「膝を打つ」実践的なユースケース
「便利そうだけど、いつ使うの?」という後輩には、このサンプルを見せてやってほしい。フロントエンド開発で最も頻出する「動的なイベントハンドラの型定義」だ。
例:コンポーネントのPropsを自動生成する
例えば、あるUIコンポーネントが `click` や `change` といったイベントを持つとき、それに対応する `onClick` や `onChange` というプロパティ型を自動で作りたいとする。
type BaseEvents = “click” | “change” | “mouseover”;
// テンプレートリテラル型と Capitalize を組み合わせる
// “on” + “Click” のように、先頭を大文字にして結合する
type EventHandlers = {
[K in BaseEvents as `on${Capitalize
};
/
生成される型:
type EventHandlers = {
onClick?: (event: any) => void;
onChange?: (event: any) => void;
onMouseover?: (event: any) => void;
}
/
const myButtonProps: EventHandlers = {
onClick: (e) => console.log(“Clicked!”),
// onclick: (e) => {} // エラー! “onClick” である必要がある
};
どうだろう? `BaseEvents` に新しいイベントを追加するだけで、すべてのハンドラ型が同期される。これこそが「型による自動化」だ。
3. ブラウザの裏側とコンパイラの挙動
ここで少し、アーキテクトらしい深掘りをしよう。
これらの `Uppercase` や `Lowercase` は、他の型のように `type.d.ts` を開いても、具体的な実装(ロジック)は書かれていない。
// TypeScriptの定義ファイルの中身はこうなっている
type Uppercase = intrinsic;
この `intrinsic`(本質的な、固有の)というキーワードが示す通り、変換処理は TypeScriptコンパイラのC++(またはRust/Go等の実装)レベルで直接処理されている。
重要なのは、これらは実行時(ランタイム)には一切影響を与えないという点だ。
JavaScriptにコンパイルされた瞬間、これらの型情報は霧のように消える。ブラウザのV8エンジンが受け取るのは、ただの文字列だ。
だからこそ、我々は「型が正しいからといって、値が正しいとは限らない」という境界線を意識しなければならない。APIから返ってくる値が本当に大文字かどうかは、実行時のバリデーション(zodなど)が必要だ。型はあくまで「開発時の契約」であることを忘れないでほしい。
4. 応用編:スネークケースをキャメルケースに変換する「型パズル」
中級者の君なら、もう少し複雑なことに挑戦したくなるはずだ。
バックエンドから送られてくる `user_name` を `userName` として扱いたい……そんな時の「型変換の極致」がこれだ。
type SnakeToCamel = S extends `${infer T}_${infer U}`
? `${Lowercase
: Lowercase;
// 試してみよう
type T1 = SnakeToCamel<"USER_ID">; // “userId”
type T2 = SnakeToCamel<"FIRST_NAME">; // “firstName”
type T3 = SnakeToCamel<"CREATED_AT_DT">; // “createdAtDt”
/
- 解説:
- 1. `${infer T}_${infer U}` で、アンダースコアの前後を分割して抽出(infer)する。
- 2. 前半(T)を小文字にし、後半(U)を再帰的にこの型に流し込む。
- 3. 再帰から戻ってきた後半の先頭を `Capitalize` で大文字にする。
- 4. これを繰り返すことで、蛇がラクダに変わる。
/
実務でここまで複雑な型を多用しすぎると「型パズルが難解すぎて読めない」というメンテナンス性の低下を招くこともある。しかし、ライブラリの作者や、社内共通の基盤ライブラリを作るシニアエンジニアにとっては、これは必須の武器になる。
5. 最後に:チーフアーキテクトからのアドバイス
文字列操作の型は、単に「見た目を整える」ためのものではない。
それは、「ドメインのルールを型システムに閉じ込める」ための強力な手段だ。
- `Uppercase` を使って、国際化(I18N)のロケールコードを厳格に管理する。
- `Uncapitalize` を使って、クラス名とインスタンス名のマッピングを定義する。
こうした工夫の一つひとつが、数ヶ月後の自分や、新しくチームに入ってきたメンバーを救うことになる。
型定義を「面倒な作業」と思うか、「堅牢な城を築くための設計図」と思うか。その意識の差が、エンジニアとしての市場価値を分けると言っても過言ではない。
今日学んだ `Uppercase`, `Lowercase`, `Capitalize`, `Uncapitalize`。明日からのコードレビューで、もし文字列の命名規則に苦しんでいる後輩がいたら、そっとこの知識を授けてやってくれ。
「その文字列、もっと賢く(TypeScriptに)管理させてみないか?」と。
健闘を祈る。

コメント