【入門編】 NoInferユーティリティ型による推論制御 – TypeScript実践ガイド

こんにちは!TypeScriptの型定義、毎日お疲れ様です。

「よし、完璧なコンポーネントができたぞ!」と意気込んでコードを書いたはいいものの、なんだか赤波線だらけになったり、意図しない型に勝手に解釈されて「なんでそうなるの!?」と頭を抱えた経験、ありませんか?……大丈夫ですよ、最初はみんなそこで盛大につまずきます。私も何度もコーヒーをこぼしそうになりました。

今日は、TypeScript 5.4で仲間入りした`NoInfer`(ノー・インファー)という、ちょっと名前は強面だけど実はめちゃくちゃ優しいユーティリティ型について、お話しさせてください。

ジェネリクスの「お節介すぎる推論」に悩まされたことがある方なら、きっと今日の記事で「おぉ、そういうことか!」と膝を打っていただけるはずです。さあ、一緒に紐解いていきましょう!

—

そもそも、TypeScriptの「推論」ってなに?

TypeScriptの最大の魅力といえば、賢い「型推論(Type Inference)」ですよね。
例えば、次のようなコードを書いてみたとします。

const message = “こんにちは、TypeScript!”;
// TypeScript「ふむふむ、文字列が入ったから、messageは string 型だな!」

私たちがわざわざ `: string` と書かなくても、コンパイラが「ここに代入されたのは文字列だから、きっとこういう型だろう」と勝手に察してくれる機能です。これがあるおかげで、私たちは日々の開発を爆速で進められています。

ですが、この「察してくれる能力(推論)」が、時にはお節介になりすぎることがあるんです。特に、関数にジェネリクス(``のような仕組み)を組み合わせたとき、そのお節介が牙を向くことがあります。

—

身近な例え話:「自動でお会計の金額を決めちゃうレジ」

ちょっと想像してみてください。
あなたが近所の八百屋さんで、お買い物をしているとします。

あなたは今、手元に「100円」という確実な基準(ベースとなる型)を持っています。お店のルールとしても「100円を基準に計算します」と決まっているとしましょう。

ところが、目の前にある「超ハイテク自動レジ」が、あなたの持っている100円を見るなり、こう言いました。
> レジ「お客さん、なんだか今日は気前が良さそうだから、自動的に『1万円』までお財布の枠を広げておきますね!」

……いやいや、ちょっと待って!私は100円のつもりで計算してほしいのに、なんで勝手に「1万円(広い型)」に財布の枠を広げられなきゃいけないの!?困りますよね。

TypeScriptのジェネリクスでも、これと全く同じ現象が起きます。
関数に「これとこれの型を一致させてね」とお願いしたつもりが、TypeScriptが気を利かせて「じゃあ、どっちにも合わせられるように、一番広い型(例えば `string | number` とか)に自動で拡張しちゃおっと!」とやってしまうのです。

これが、私たちが意図しない型エラーに悩まされる原因なんですね。

—

TypeScript 5.4の救世主、「NoInfer」の登場

そんな「お節介な推論」にストップをかけてくれるのが、TypeScript 5.4で導入された`NoInfer`です!

`NoInfer` は、英語の「No(〜ない)」と「Infer(推論する)」をくっつけた造語です。日本語にするなら「おい、そこは勝手に推論するなよ!」という、TypeScriptへの優しくも厳格なダメ出しになります。

百聞は一見にしかず。コードでその違いを見てみましょう。

困った例:お節介な推論に振り回される世界

まずは、`NoInfer` を使わない、昔ながらの(そしてよくあるバグの温床になる)コードです。

// 基準となるテーマカラーのリストと、実際に選択されたカラーを受け取る関数
function chooseThemeColor(
defaultColor: T,
selectedColor: T
): T {
return selectedColor;
}

// 基準として “red” を渡したい
const result = chooseThemeColor(“red”, “blue”);
// あれっ? “blue” は “red” と違う文字列だけど、
// TypeScript「どっちも string だから、T は ‘red’ | ‘blue’ に広げちゃおう!」と解釈してしまう。

本当は「第1引数の `defaultColor` の型を絶対の基準(ボス)にして、第2引数はそれに従わせたい」だけなのに、TypeScriptは両方を見て型 `T` を勝手に調整(拡張)してしまいます。これでは型安全の網の目がすり抜けてしまいますよね。

解決例:NoInferで「ボス」を固定する!

ここで、`NoInfer` の出番です。第2引数に対して「ここは推論に参加しないでね(ボスに従ってね)」と指定してみます。

// 第2引数の型を NoInfer でガードする!
function chooseThemeColorSafe(
defaultColor: T,
selectedColor: NoInfer // ← ここに注目!
): T {
return selectedColor;
}

// 1. 正しいパターン(基準と同じ仲間なのでOK)
const okColor = chooseThemeColorSafe(“red”, “pink”);
// T は “red” に固定され、第2引数の “pink” も string の枠内なのでセーフ!

// 2. 意図しないパターン(おや?文字型じゃないものが混ざっているぞ…?)
// const ngColor = chooseThemeColorSafe(“red”, 123);
// 🚨 ここでTypeScriptが「おいおい!第1引数は string (‘red’) なのに、第2引数に数値 (123) が来てるぞ!」と
// 怒って赤波線を教えてくれます。

どうでしょう? `NoInfer` を挟むことで、「第1引数(`defaultColor`)こそが絶対的な基準(ボス)であり、第2引数はそれに合わせるだけの従者である」という主従関係を、TypeScriptにハッキリと伝えることができるようになりました。

これが、`NoInfer` の真骨頂です。

—

実務で使える!Web制作・開発での活用シーン

「なるほど、仕組みは分かったけど、実際のWeb制作やアプリ開発でどんな時に使うの?」という声が聞こえてきそうですね。

よくある実務の現場では、「設定値の初期値オブジェクト」と「ユーザーが上書きする設定オブジェクト」を受け取るヘルパー関数などで大活躍します。

// アプリの設定を表す型
interface AppConfig {
theme: “light” | “dark”;
retries: number;
}

// 設定を更新する関数
function updateConfig(
baseConfig: T,
// ユーザーからのパッチ(一部の変更)を受け取るが、
// baseConfig の型(T)を勝手に書き換えさせたくない!
patch: Partial>
): T {
return { …baseConfig, …patch };
}

// 実際の使い方
const myConfig: AppConfig = { theme: “light”, retries: 3 };

const updated = updateConfig(myConfig, {
theme: “dark”, // 正しい設定項目なのでOK
// 誤って存在しないプロパティや、違う型を入れようとすると
// inventory: 100, // 🚨 型エラーで未然に防げる!
});

もし `NoInfer` を使っていなかったら、ユーザーがパッチを渡した瞬間に `T` の型が書き換わってしまい、思わぬバグを生む原因になっていたかもしれません。それをスソの段階でピシッと防いでくれるのです。

—

まとめ:型のコントロール権を取り戻そう

いかがでしたでしょうか?
TypeScriptの `NoInfer` ユーティリティ型について、少しでもイメージが湧いてきたでしょうか?

  • TypeScriptの自動推論はお節介になりすぎることがある
  • ジェネリクスで「基準となる型」を固定したい時に `NoInfer` が最高の相棒になる
  • 「ここは勝手に推論しないで!」とコンパイラに意思表示するための強力な武器

最初は「なんだか難しそうな名前だな……」と身構えてしまうかもしれませんが、使い所が分かると「なんてかゆい所に手が届く機能なんだ!」と手放せなくなるはずです。

もし日々の開発で「なんで俺の意図した型になってくれないんだよ〜!」と叫びたくなったら、思い出してください。「あ、ここに `NoInfer` を使って、コンパイラを落ち着かせればいいんだな」と。

あなたのTypeScriptライフが、より快適でエラーの少ないものになりますように。
それでは、また次回の現場の知恵袋でお会いしましょう!

コメント

タイトルとURLをコピーしました