【TypeScript】any型が危険な理由と4つの回避策
TypeScriptでany型を指定すると、それまで赤い波線で出ていた型エラーがすっと消えて、思わずほっとした経験はありませんか。
anyは「その値の型チェックをまるごと無効化する型」で、1つ放置すると周囲の値まで型安全が崩れていきます。この記事では、anyが危険に働く仕組み、置き換えるための4つの回避策、そして使ってよい例外のラインまでを見ていきます。anyを見かけたときに、自分で判断できる状態を目指しましょう。
この記事は次のような方におすすめです。
- any型を見かけて、使っていいのか迷っている方
- anyにすると型エラーが消える理由を知りたい方
- anyの代わりに何を使えばいいか判断できるようになりたい方
- チームでanyの混入を防ぎたい方
読み終えるころには、anyを目にしても「そのまま放置するのか、何に置き換えるのか」を自分の言葉で決められるようになります。
それでは、順を追って詳しく見ていきましょう!
- 未経験で後悔したくない
【実体験】未経験からITエンジニアに転職して後悔した話|4社経験してわかった「最初の選択ミス」 - 年収が低くて不安
エンジニア転職体験談|4社で年収250→510万にした全記録
TypeScriptのany型とは(型チェックを無効にする型)
anyは、その値に対する型チェックをすべて止めてしまう型です。any型の値には、どんな型の値でも代入でき、存在しないプロパティの参照や未定義のメソッド呼び出しも、コンパイル時にはエラーになりません。
let value: any = 10;
value = "テキスト"; // 文字列を入れてもOK
value = { id: 1 }; // オブジェクトを入れてもOK
const n: number = value; // any は number 型の変数へも代入できる
value.foo.bar; // 存在しないプロパティ参照もエラーにならない
value.run(); // 定義していないメソッド呼び出しもコンパイルを通る
上のコードは、どの行もtscで型エラーになりません。通常なら弾かれる代入やアクセスが素通りするのは、anyが型チェックそのものを迂回させているからです。TypeScript公式ドキュメントのTypeScript Handbook「Everyday Types」anyでも、anyは型チェックを避けたいときに使う特別な型として扱われています。つまりanyは、便利な万能型というより型安全のスイッチを切るための型だと考えておくとよいでしょう。
明示的なanyと暗黙のany(noImplicitAny)
anyには、自分で : any と書く明示的なものだけでなく、書いていないのに付いてしまう暗黙のanyがあります。型注釈のない関数の引数などで、TypeScriptが型を決められないときに起こります。
function greet(name) { // 引数 name に型注釈がない
return "Hello, " + name;
}
この挙動は設定によって変わります。noImplicitAny が無効なら暗黙のanyは黙って許容されますが、有効にすると次のようなエラーになります。
Parameter 'name' implicitly has an 'any' type. ts(7006)
noImplicitAny は、型を書き忘れた場所を暗黙のanyとして見逃すか、エラーとして知らせるかを切り替える設定です(TSConfig Reference「noImplicitAny」)。知らないうちにanyが紛れ込む原因の多くは、この暗黙のanyなので、まずここを防げるかどうかが安全性を大きく左右します。
なぜany型は危険なのか(3つの実害)
anyの怖さは、型エラーが消えて一見うまくいっているように見える裏で、次の3つが静かに壊れる点にあります。
- コンパイルは通るのに、実行時に壊れる
- 想定と違う結果を、エラーを出さずに生む
- 補完・リファクタリング・定義ジャンプが効かず、開発体験が落ちる
1つ目は、型チェックが素通りするために起こります。
const data: any = { name: "Alice" };
data.profile.age; // profile は存在しないのにコンパイルは通る
data.profile は存在しないため、本来なら型エラーとして気づきたいところです。しかしanyを使うとtscでは止まらず、実行時に profile が undefined のまま参照されてエラーになります。コンパイルが通ることと、実行時に正しく動くことはまったく別物だという境界が、anyでは見えなくなります。なお、実際に投げられる例外の文言は実行環境やTypeScriptバージョンによって異なる場合があります。
2つ目は、型が効かないまま演算や結合が進み、意図と違う値が黙って作られるケースです。
const price: any = "1000"; // 実は文字列
const total = price + 100; // 数値のつもりが "1000100" という文字列連結になる
3つ目は開発体験の劣化です。any型の値はエディタが中身を把握できないため、プロパティ補完が出にくく、名前を変えるリファクタリングでも追従されにくくなります。定義元へのジャンプも効かず、調査に時間がかかることがあります。目に見えるエラーが出ない分、anyの問題は動かして初めて表面化しやすい点に注意が必要です。
anyは周囲の値へ広がる
anyの影響が1か所で止まらないのは、anyの値を代入・演算・分割代入した先の型も、推論に任せるとanyになりやすいからです。
const raw: any = fetchData();
const id = raw.id; // id も any
const name = raw.user.name; // name も any
const [first] = raw.items; // first も any
これらは型注釈を書いていないため、anyがそのまま引き継がれます。一方で、代入先に型注釈を付ければ安心、というわけでもありません。
const count: number = raw.count; // number と宣言できてしまう
count.toFixed(); // count が実は文字列でもコンパイルは通る
count は静的には number として扱われますが、any由来の値は代入時のチェックをすり抜けるため、実体が数値である保証はありません。anyは1か所にとどまらず、触れた先の型安全まで弱くしてしまう性質があります。だからこそ、外部データや型のないライブラリなどから自分の意図とは関係なくanyで入ってくる値は、そのまま使い回さず、条件で判定して具体的な型へ絞り込んでから使うのが基本になります。
any型を回避する4つの方法
anyへの対処は、大きく「anyの代わりに何を書くか(置き換え)」と「そもそもanyを入れない仕組みを整える(防止)」の2方向に分かれます。前者はコードで書く3つ、後者は設定で守る1つで、合わせて次の4つを状況に応じて使い分けます。
- 値の型が不明なら、安全な受け皿に入れてから絞る(unknown)
- 値を判定して型を絞り込む(型ガード)
- 型を固定せず汎用的に型付けする(ジェネリクス)
- そもそもanyを入れない仕組みを設定で整える(noImplicitAny / ESLint)
状況ごとの選び方を一覧にすると、判断が速くなります。
| 状況 | 使うもの | ねらい |
|---|---|---|
| 値の型が実行時まで分からない | unknown | まず安全に受け、使う直前に型を確認する |
| 値の中身によって処理を変えたい | 型ガード | 判定して具体的な型に絞る |
| 型を固定せず汎用的に使い回したい | ジェネリクス | 呼び出し側の型を保ったまま受け渡す |
| そもそも混入自体を防ぎたい | noImplicitAny / ESLint | 自動的に検出して防ぐ |
unknownで安全に受けてから型を絞る
最初の受け皿になるのがunknownです。anyと違い、unknownは中身を確認するまで操作できません。
function handle(input: unknown) {
input.toUpperCase(); // 例:'input' is of type 'unknown'. ts(18046)(バージョンにより文言が異なる場合あり)
if (typeof input === "string") {
input.toUpperCase(); // string と確認済みなので安全に呼べる
}
}
型を確認しないまま呼ぶとエラーになり、確認して初めて操作できるため、anyのような素通りが起きません。
型ガードで判定して具体的な型に絞る
値の中身によって処理を分けたいときは、型ガードで判定して具体的な型に絞り込みます。typeofやinなどで値を確認すると、その分岐の中では確認済みの型として扱われ、anyのように素通りさせずに済みます。
function format(value: string | number) {
if (typeof value === "number") {
return value.toFixed(2); // ここでは number 確定 → number のメソッドが使える
}
return value.toUpperCase(); // 残りは string 確定 → string のメソッドが使える
}
分岐ごとに型が1つに定まるため、toFixed を文字列に対して呼ぶようなミスはコンパイル時点で弾かれます。unknownで受け取った値を使う直前に絞り込むときも、この型ガードがそのまま働きます。
ジェネリクスで型を保ったまま受け渡す
型を1つに決め打ちしたくないときは、ジェネリクスで受けます。渡された型をそのまま保てるので、anyのように情報を捨てずに使い回せます。
function wrap<T>(value: T): T[] {
return [value];
}
const a = wrap("x"); // a は string[] として推論される
const b = wrap(1); // b は number[] として推論される
3つの置き換え先のどれを選ぶか迷ったら、次の順で整理すると判断しやすくなります。
要点は、書き換えるなら「不明ならunknown・絞るなら型ガード・汎用ならジェネリクス」、そもそも入れさせないなら設定で防ぐと整理すれば迷いにくい、ということです。
noImplicitAnyとESLintでanyを自動検出する
人の注意だけでanyを防ぐのは限界があるため、設定で自動的に検出するのが確実です。まず暗黙のanyは noImplicitAny で防ぎます。
{
"compilerOptions": {
"noImplicitAny": true
}
}
一方、明示的に書かれた : any はコンパイラが許容するため、こちらはESLintのルールで検出します。
// eslint.config.js
export default [
{
rules: {
"@typescript-eslint/no-explicit-any": "error"
}
}
];
役割を整理すると、暗黙のanyはnoImplicitAny、明示的なanyはESLintの no-explicit-any と、担当を分けて防ぐ形になります(typescript-eslint「no-explicit-any」)。両方を有効にすれば、型の書き忘れも明示的なanyも自動で検出できます。
それでもany型を使ってよいケース
anyを常に悪いもの、と決めつける必要はありません。次のような場面では、条件付きで許容されることがあります。
- JavaScript資産をTypeScriptへ段階的に移行している途中の、一時的なany
- 型付けにかかる手間が見合わない箇所(複雑な外部由来データを一時的に扱う場合など)
- 一部のテストコードなど、型の厳密さより検証の手軽さを優先したい場面
いずれも「今は割り切る」判断であり、放置してよい理由ではありません。anyは禁止事項というより最終手段で、使うなら範囲と期限を意識して限定的にとどめるのが安全です。
なお、anyの代わりに型アサーションの as を使うのは要注意です。as はコンパイラに「この型として扱う」と伝えるだけで、値の実体は変えません。そのため、宣言と実際がずれていても実行時に破綻することがあり、anyの安全な置き換えにはなりません。
【付録】さらに学びを深めるためのリソース
さらにTypescriptの学習を進めたい方のために、いくつかのリソースを紹介します。
これらのリソースを活用することで、TypeScriptの型システムについてより深い知識を得ることができるでしょう。
おすすめの書籍
ゼロからわかる TypeScript入門
技術評論社から出版されている「ゼロからわかる TypeScript入門」は、プログラミング初心者や本職プログラマーではない方を主な対象にした入門書です。
変数・条件分岐・ループといった基本から、クラスやインターフェース、モジュールまで段階的に学べる構成になっています。最終章ではWeb APIとJSONを使った非同期Webアプリの作成も体験できるので、「実際に動くものを作る」ところまで到達できます。
プロを目指す人のためのTypeScript入門
技術評論社の「プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで」、通称 ブルーベリー本 です。
JavaScriptの仕様とTypeScript独自の機能を両方押さえつつ、リテラル型・ユニオン型・keyof型・ジェネリクスなど、高度な型表現まで踏み込んで解説しています。TypeScriptの型システムの表現力を本格的に学べる一冊です。
オンラインで参照できる公式ドキュメント
TypeScript公式ハンドブック
https://www.typescriptlang.org/docs/
TypeScriptの公式ドキュメントです。
intersection型を含む、すべての型システムの機能について詳細な説明があります。
TypeScript Deep Dive
https://basarat.gitbook.io/typescript/
TypeScriptの深い部分まで掘り下げて解説しているオンラインブックです。
無料で読むことができ、intersection型についても詳しく説明されています。
TypeScriptの学習は終わりがありません。
新しい機能が常に追加され、より良い書き方が発見されています。
継続的に学習を続けることで、より良いTypeScriptプログラマーになれるはずです。
まとめ – anyは無効化スイッチ、見つけたら回避策で対処する
この記事の要点をまとめます。
- anyはその値の型チェックをまるごと無効化する型で、放置すると周囲の値まで影響が広がる
- 実害は「コンパイルは通るのに実行時に壊れる/想定と違う結果を静かに生む/補完やリファクタが効かない」の3つ
- 回避は「不明ならunknown・絞るなら型ガード・汎用ならジェネリクス・混入防止はnoImplicitAnyとESLint」で選ぶ
- 移行中や型付けの手間が見合わない箇所など、条件を絞れば使ってよい場面もある
anyを見かけたときに「そのまま放置するのか、何に置き換えるのか」を判断できれば、型安全はかなり守りやすくなります。
※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。






