TypeScript:型推論とany
1学習の目的
- TypeScriptが型を自動で推測してくれることを理解し、型注釈を書く場所と省く場所を判断できるようになる。
- any と unknown の違いを理解する。any は型チェックを完全に無効にするが、unknown は安全に扱える——この差が事故を防ぐ。
2基礎解説
ここまで全ての変数に型注釈を書いてきましたが、実は多くは省略できます。TypeScriptは代入された値から型を推測してくれるからです。
| 書き方 | 推論される型 | 注釈は必要か |
|---|---|---|
| const n = 42 | number | 不要 |
| const s = "A" | "A"(リテラル型) | 不要 |
| let s = "A" | string | 不要 |
| function f(x) | 暗黙のany | 必須 |
- 変数の型注釈はほとんど省略できる。const price = 128000; と書けば自動的に number になる。冗長な注釈はかえって読みにくい。
- 関数の引数は省略できない。function f(x) と書くと「暗黙のany」となり、厳格モードではエラーになる。引数には必ず型を書く。
- const で文字列を宣言するとリテラル型(その値そのもの)に推論される。let なら string。この違いはTS06で重要になる。
- any は存在しないプロパティへのアクセスまで通してしまう。v.foo.bar.baz のような明らかな誤りも素通りする。
- unknown は「型が分からない」を安全に表す。any と違い、型を確かめるまで何もできないので、確認を強制できる。外部から来るデータに向いている。
現場使用例:型注釈を省いて簡潔に書く、関数の引数には必ず型を書く、外部データは unknown で受けて検証してから使う。「どこに型を書くか」の判断が、読みやすさを左右する。
// 型注釈は省略できる(自動で推論される)
const price = 128000; // number
const productName = "PC"; // string
const items = [1, 2, 3]; // number[]
const obj = { name: "A", price: 100 }; // { name: string; price: number }
// ❌ 関数の引数は省略できない(暗黙のany)
function bad(x) {
return x * 2;
}
// → Parameter 'x' implicitly has an 'any' type.
// ✅ 引数には型を書く(戻り値は推論に任せてよい)
function good(x: number) {
return x * 2; // number と推論される
}
// ⚠ any は何でも通ってしまう
const danger: any = "文字列";
console.log(danger.foo.bar.baz); // エラーにならない(実行時に落ちる)
// ✅ unknown は確認するまで使えない
const safe: unknown = "文字列";
// console.log(safe.toUpperCase()); → エラー
if (typeof safe === "string") {
console.log(safe.toUpperCase()); // ここでは string として扱える
}
3基本ドリル(10問)
出力 == 140800
const price = 128000; だけで number と推論される。
const price = 128000; const taxed = Math.round(price * 1.1); console.log(taxed);
型注釈が無くても型は付いている。price は number と推論され、文字列を代入しようとすればエラーになる。
A. 自動で推論される B. 厳格モードでエラーになる C. number になる D. 問題ない 選択★☆☆無料
解答 == B
引数には代入される値が無いので、推論の材料が無い。
B
Parameter 'x' implicitly has an 'any' type というエラーになる。関数がどう呼ばれるか分からないため、TypeScriptは推論できない。
出力 == 42
function multiply(a: number, b: number) { return a * b; } と書く。戻り値の型は不要。
function multiply(a: number, b: number) {
return a * b;
}
console.log(multiply(6, 7));
戻り値の型は中身から推論される。ただし複雑な関数では、明示したほうが意図が伝わる場合もある。
const data: ____ = "こんにちは";
if (typeof data === "string") {
console.log(data.length);
}
出力 == 5
any より安全な「型が分からない」を表す型。小文字で7文字。
unknown
unknown は確認するまで何もできない。typeof で型を確かめて初めて、その型として扱えるようになる。
A. 実行前にエラーになる B. エラーにならず、実行時に落ちる C. undefined が返る D. 型が推論される 選択★☆☆無料
解答 == B
any は型チェックを無効にする。
B
any は存在しないプロパティの連鎖まで通してしまう。TypeScriptを使っているのに、JavaScriptと同じ実行時エラーで初めて気づくことになる。
出力 == マウス:3,200円
const product = { name: "マウス", price: 3200 }; だけでよい。
const product = { name: "マウス", price: 3200 };
console.log(`${product.name}:${product.price.toLocaleString()}円`);
オブジェクトの型も自動で推論される。{ name: string; price: number } と書いたのと同じ扱いになるので、product.stock と書けばエラーになる。
function half(n: ____) {
return n / 2;
}
console.log(half(84));
出力 == 42
数値を表す型。
number
引数の型は必須、戻り値の型は任意——これが基本の使い分け。この関数の戻り値は number と正しく推論される。
A. どちらも string B. const は "固定" というリテラル型、let は string C. どちらもリテラル型 D. const は any 選択★★☆無料
解答 == B
const は再代入できないので、値が変わらないことが確定している。
B
const は値が変わらないので、その値そのものが型になる。この性質はTS06のリテラル型で重要な意味を持つ。
出力 == 84
typeof value === "number" で確認する。確認しないと使えない。
const value: unknown = 42;
if (typeof value === "number") {
console.log(value * 2);
} else {
console.log("数値ではありません");
}
unknown は確認を強制する。この if を書かずに value * 2 と書くとエラーになるので、検証を忘れることがない。
出力 == 1
filter を使う。配列も推論されるので型注釈は不要。
const prices = [128000, 3200, 8500]; const expensive = prices.filter((p) => p >= 10000); console.log(expensive.length);
filter の中の引数 p も number と推論される。配列の型が分かっているので、その中身の型も自動的に決まる。
4実践シナリオ(5問)
出力 == 合計:131,200円
配列もreduceの結果も推論に任せる。関数を作る場合は引数にだけ型を書く。
const items = [
{ name: "ノートPC", price: 128000 },
{ name: "マウス", price: 3200 },
];
const total = items.reduce((sum, i) => sum + i.price, 0);
console.log(`合計:${total.toLocaleString()}円`);
TS03で書いた同じ処理より、はるかに読みやすい。型は全て推論されているので、安全性は変わらないまま記述量だけが減っている。
出力 == undefined
const data: any = { name: "PC", price: 100 }; のあと data.stock を出力する。
const data: any = { name: "ノートPC", price: 128000 };
console.log(data.stock);
型注釈を書いているのに、まったく守られていない。もし { name: string; price: number } と正しく型を書いていれば、この行は実行前にエラーになっていた。
出力 == 140800
typeof で確認してから Number() で変換する。
const input: unknown = "128000";
if (typeof input === "string") {
const price = Number(input);
console.log(Math.round(price * 1.1));
}
外部から来るデータは unknown で受けるのが安全。APIのレスポンスやフォームの入力など、何が来るか分からないものは、確認してから使う。
出力3行。数値:42 / 文字列:あ / 不明な型
引数の型は unknown。typeof で分岐する。戻り値は string。
function describe(value: unknown): string {
if (typeof value === "number") {
return `数値:${value}`;
}
if (typeof value === "string") {
return `文字列:${value}`;
}
return "不明な型";
}
console.log(describe(42));
console.log(describe("あ"));
console.log(describe(true));
if の中では、その型として扱える。typeof value === "number" を通った先では value は number 型になっている——これを型の絞り込みと呼び、TS09で詳しく扱う。
「在庫総額:927,400円」
「平均単価:46,566.7円」 コーディング★★★無料
出力2行が完全一致
平均単価は価格の合計÷件数。toFixed(1) では3桁区切りにならないので、toLocaleString に小数の桁数を指定する方法もあるが、ここでは Number(avg.toFixed(1)).toLocaleString() で対応する。
const items = [
{ name: "ノートPC", price: 128000, stock: 5 },
{ name: "マウス", price: 3200, stock: 42 },
{ name: "キーボード", price: 8500, stock: 18 },
];
const totalValue = items.reduce((sum, i) => sum + i.price * i.stock, 0);
const avgPrice = items.reduce((sum, i) => sum + i.price, 0) / items.length;
console.log(`在庫総額:${totalValue.toLocaleString()}円`);
console.log(`平均単価:${Number(avgPrice.toFixed(1)).toLocaleString()}円`);
型注釈が1つも無いのに、全ての変数に型が付いている。items の構造から i.price の型まで芋づる式に推論されるので、打ち間違いはやはり検出される。
5仕上げ課題
受け取るデータ(unknown 型で宣言すること)
const input: unknown = { name: "ノートPC", price: "128000", qty: 2 };
※ price が文字列で届いている点に注意
処理
① input がオブジェクトかどうか確認する(typeof input === "object" && input !== null)
② オブジェクトとして扱うため、as { name: string; price: string; qty: number } で型を指定する
③ price を数値に変換する
④ 小計(price × qty)を計算する
⑤ 結果を出力する。オブジェクトでなければ「不正なデータです」と出力
変数の型注釈は、必要な箇所(unknown の宣言)以外は書かないこと。
期待される出力(3行)
商品:ノートPC
単価:128,000円 × 2
小計:256,000円
出力3行が完全一致
unknown のままではプロパティにアクセスできない。typeof で確認したあと、as を使って「この型として扱う」と指定する(型アサーション)。price は文字列なので Number() で変換する。
const input: unknown = { name: "ノートPC", price: "128000", qty: 2 };
if (typeof input === "object" && input !== null) {
const data = input as { name: string; price: string; qty: number };
const price = Number(data.price);
const subtotal = price * data.qty;
console.log(`商品:${data.name}`);
console.log(`単価:${price.toLocaleString()}円 × ${data.qty}`);
console.log(`小計:${subtotal.toLocaleString()}円`);
} else {
console.log("不正なデータです");
}
この課題には、実務でそのまま出てくる2つの落とし穴が仕込まれている。
1つ目は price が文字列で届いていること。JSONやフォームから来るデータでは頻繁に起きる。"128000" * 2 はJavaScriptでは 256000 になってしまい(数値に変換されるため)気づきにくいが、文字列のまま扱えば "128000128000" のような結果になる場面もある。型として「ここは文字列」と明示し、変換を挟むことで事故を防いでいる。
2つ目は as という書き方だ。これは型アサーションと呼ばれ、「TypeScriptよ、これはこの型だと思ってくれ」と開発者が保証する宣言になる。便利だが、実際にその型である保証はどこにも無い——嘘をつけば実行時に落ちる。any ほどではないが、これも慎重に使うべき道具だ。
だからこそ、typeof でオブジェクトかどうかを先に確認している。何の確認もせずに as だけ書くのは、any を使うのとほとんど変わらない。「確認してから断言する」——この順序が安全性を支えている。
次章ではユニオン型を学ぶ。「文字列または数値」のように、複数の可能性を型として表現できるようになり、as に頼らずに済む場面が増える。