やあ、元気にしてるかな。現場でコードをバリバリ書いていると、必ずぶち当たる壁がある。それが「型が広すぎて扱いづらい」という問題だ。
APIから返ってくるユニオン型、あるいは複雑に組み合わさった既存の型定義……。それらの中から「特定の不純物」だけを綺麗に取り除き、純度の高い型を取り出したい。そんな時に、僕らシニアエンジニアが密かに、そして当たり前のように使いこなしているのが「Conditional Typesと`never`を組み合わせたフィルタリング」だ。
今日は、公式ドキュメントをなぞるだけでは決して辿り着けない、このテクニックの本質と「なぜそう動くのか」という裏側のメカニズムを徹底的に解説しよう。
—
なぜ `never` なのか? その真の正体を知る
まず、君に問いかけたい。`never` 型を単なる「値が存在しないことを示すエラー用の型」だと思っていないかい?
確かに、関数の戻り値が例外を投げる場合などに `never` は使われる。しかし、型定義の文脈において `never` は「集合論における空集合(Empty Set)」として機能するんだ。
ここが重要なポイントなのだが、TypeScriptのユニオン型において、`never` は「合体しても相手を変化させない(恒等元)」という性質を持っている。
type Result = string | number | never;
// これの結果は ‘string | number’ になる。neverは消滅するんだ。
この「特定の条件で `never` を返せば、その型はユニオンから消える」という性質を利用するのが、型フィルタリングの極意だ。
—
魔法の杖:Distributive Conditional Types(分配法則)
さて、次に理解すべきは「条件付き型(Conditional Types)」の挙動だ。
type IsString
これ自体は単純だが、ジェネリック型 `T` にユニオン型を渡したとき、TypeScriptは魔法のような動きを見せる。これを「分配(Distributive)」と呼ぶ。
もし `T` が `string | number` だった場合、TypeScriptは内部でこう解釈するんだ。
`(string extends string ? true : false) | (number extends string ? true : false)`
結果として `true | false` になる。この「一つずつバラして判定する」という性質と、先ほどの「`never` は消える」という性質を組み合わせると、特定の型を間引くフィルターが完成する。
—
実践:特定の型を型定義から「蒸発」させる
では、現場ですぐに使える実用的なコードを見ていこう。例えば、複雑なレスポンス型から「関数」や「null」だけを綺麗に取り除きたいケースを想定してみる。
/
- 渡されたユニオン型 T から、型 U に割り当て可能な要素を「除外」する
- これが組み込みの Exclude
の正体だ。
/
type MyExclude
/
- 逆に、特定の型「だけ」を抽出するフィルター
/
type MyExtract
// — 現場での活用例 —
type RawData = string | number | (() => void) | null | undefined;
// 1. 関数とnull/undefinedを排除して、純粋なデータ型(string | number)だけを取り出す
type CleanData = MyExclude
// 2. string型だけに絞り込む
type OnlyString = MyExtract
/
- 【解説】
- MyExclude
が動くとき、内部ではこうなっている: - 1. (string extends Function ? never : string) => string
- 2. (number extends Function ? never : number) => number
- 3. (() => void extends Function ? never : (() => void)) => never
- 4. …
- 最終的に string | number | never | … となり、neverは消えるため
- 結果として string | number だけが残る。美しいだろう?
/
—
ブラウザの裏側とTypeScriptの「消去」
ここで少し、アーキテクトらしい視点を共有しておこう。
こうした `never` を使った高度な型パズルは、ブラウザ上で実行されるときには影も形も残っていない。 TypeScriptの型システムは、コンパイル(トランスパイル)時にすべて「消去(Erasure)」されるからだ。
「じゃあ、実行時のパフォーマンスには関係ないのか?」
その通り。しかし、「開発時の安全性」と「コードの自己文書化」という点では、計り知れない恩恵がある。
不適切な型が混じり込むのをコンパイル段階で阻止できれば、実行時に `undefined` のプロパティを叩いてブラウザがクラッシュする(あの忌々しい `Uncaught TypeError` だ)可能性を、デプロイ前にゼロに近づけることができるんだ。
—
応用編:オブジェクトのプロパティをフィルタリングする
中級者の君なら、もう一歩踏み込んでみよう。オブジェクトのキーを型でフィルタリングする手法だ。これは実際のライブラリ開発や、大規模な状態管理(ReduxやZustandなど)の設計で頻出する。
/
- オブジェクト T から、値の型が V であるプロパティ名だけを抽出する
/
type KeysOfType
[K in keyof T]: T[K] extends V ? K : never;
}[keyof T];
// 実践的なサンプル
interface UserProfile {
id: number;
name: string;
age: number;
email: string;
isActive: boolean;
updateEmail: (newEmail: string) => void;
}
// UserProfileの中から「文字列型」のプロパティ名だけを抜き出す
// 1. まず { id: never; name: “name”; age: never; email: “email”; … } という型ができる
// 2. その後、値のユニオンを取ることで never が消え、”name” | “email” だけが残る
type StringKeys = KeysOfType
// これを使えば、特定の型だけに反応するコンポーネントのPropsなどが作れる
function UpdateField(key: KeysOfType
// id や updateEmail を渡そうとするとコンパイルエラーになる。安全だ。
console.log(`Updating ${key} to ${value}`);
}
UpdateField(“name”, “田中”); // OK
// UpdateField(“age”, “30”); // Error: “age” は string ではないので弾かれる
—
シニアからのアドバイス:やりすぎには注意
`never` を使ったフィルタリングは強力だ。しかし、あまりに複雑な型パズルを組み上げすぎると、後輩たちがコードを読めなくなる「型パズル地獄」に陥ることもある。
コツは、「意味のある単位で型に名前をつけること」だ。
`T extends U ? never : T` と直接書くのではなく、`ExcludeData
型定義は、未来の自分やチームメンバーへの「手紙」だ。
論理的に厳密でありつつ、人間が読んでも意図が伝わる。そんな「優しくも強い」コードを書けるようになることが、真のフロントエンド・スペシャリストへの道だと僕は思う。
今回の `never` の使いこなし、ぜひ君のプロジェクトの型定義ファイル(`types.d.ts`)に忍ばせてみてくれ。きっと、コードの堅牢さが一段階上がるはずだ。
次は `infer` キーワードを使った型推論の深淵について話そうか。また会おう!

コメント