こんにちは、かつコーチです。
前回は、strictモードとtsconfig.jsonの主要オプションについて解説しました。
strictモードを有効にしても、実は簡単に型チェックをすり抜ける方法があります。
それが今回のテーマ、any型です。
「型のエラーが出て面倒だから、とりあえずanyにしておこう」——これは誰もが一度は通る道ですが、anyを使いすぎるとTypeScriptを使っている意味そのものが薄れてしまいます。
今回は、なぜanyが便利なのに危険なのか、そして代わりにどう書けばいいのかを整理していきます。
any型とは何か・なぜ危険なのか
どんな値でも受け入れてしまう型
any型は、TypeScriptにおいて「型チェックを行わない」ことを意味する特別な型です。
let value: any = 10;
value = "文字列に変えてもOK";
value = { key: "オブジェクトにもなれる" };
value.foo.bar.baz(); // 存在しないプロパティを呼び出してもエラーにならない
anyを使うと、代入もプロパティアクセスも関数呼び出しも、すべてTypeScriptのチェックから除外されます。
一見自由で便利ですが、これは実質的に「その変数だけJavaScriptに戻す」のと同じことです。
anyが増えるとTypeScriptの恩恵が消えていく
anyが1箇所あるだけなら大きな問題にはなりませんが、厄介なのはanyが伝染していくことです。
function fetchUser(): any {
// API通信の結果をそのままanyとして返している
return { name: "かつコーチ", age: 33 };
}
const user = fetchUser();
console.log(user.nmae); // タイプミスなのにエラーにならない
fetchUserの戻り値がanyだと、それを受け取ったuserもanyになり、そこから先のすべてのコードで型チェックが効かなくなります。
私も過去に、急いでいるときに「とりあえずany」を積み重ねてしまい、数週間後にリファクタリングしようとしたら、どこで何の型が使われているのか全く追えなくなっていた、という経験があります。
そのときに「anyは借金のようなもので、後で必ず利息付きで返ってくる」と痛感しました。
型が分からないときの代替手段
unknown型:安全な「わからない」を表現する
anyの代わりに検討したいのがunknown型です。
❌ Before:anyでとりあえず受け取る
function parseJson(json: string): any {
return JSON.parse(json);
}
const data = parseJson('{"name": "かつコーチ"}');
console.log(data.toUpperCase()); // 存在しないメソッドでもエラーにならず、実行時エラーになる
✅ After:unknownで受け取り、使う前に型を確認する
function parseJson(json: string): unknown {
return JSON.parse(json);
}
const data = parseJson('{"name": "かつコーチ"}');
if (typeof data === "object" && data !== null && "name" in data) {
console.log((data as { name: string }).name);
}
unknownはanyと同じく「どんな値でも入る」型ですが、決定的に違うのは、中身を使う前に型を確認しないとエラーになるという点です。
「何でも受け取れるけど、使うときは必ず身元確認をさせる」というイメージで捉えると分かりやすいです。
外部APIのレスポンスなど、実行するまで型が確定しない値を扱うときは、anyではなくunknownを第一候補にしましょう。
ジェネリクスで「型を保ったまま」柔軟にする
「いろいろな型を受け取りたいけれど、型情報は失いたくない」という場面では、ジェネリクスが有効です。
function firstItem<T>(items: T[]): T | undefined {
return items[0];
}
const firstNumber = firstItem([1, 2, 3]); // number | undefined
const firstName = firstItem(["かつ", "コーチ"]); // string | undefined
anyで書いてしまうと戻り値の型情報が失われますが、ジェネリクスの<T>を使うことで、「渡した配列の要素の型」がそのまま戻り値の型として保たれます。
anyにしたくなったときは、「本当に型が決まらないのか」「実はジェネリクスで表現できるのではないか」を一度疑ってみる価値があります。
型が複雑で書くのが大変なとき
型推論に任せられる部分は書かない
「型を書くのが面倒だからany」というパターンも多いですが、TypeScriptには型推論という仕組みがあるため、実は全部の型を手書きする必要はありません。
// 型注釈がなくても、TypeScriptが自動で型を推論してくれる
const price = 1000; // numberと推論される
const items = ["ノート", "ペン"]; // string[]と推論される
無理に: numberのような注釈をすべての変数に書こうとすると、かえって手間が増えてanyに逃げたくなります。
関数の引数と戻り値、そしてオブジェクトの構造など「外部との境界」だけ型を明示し、内部のローカル変数は推論に任せる、というメリハリをつけると、無理なく型を書き続けられます。
複雑な外部データはユーティリティ型で整理する
外部から来るデータの型が複雑な場合、anyで丸ごと諦めるのではなく、必要な部分だけを型として切り出す方法もあります。
type ApiResponse = {
id: number;
name: string;
email: string;
internalNote: string; // 画面では使わない社内用フィールド
};
// 画面表示で使う項目だけを抜き出した型
type DisplayUser = Pick<ApiResponse, "id" | "name" | "email">;
function showUser(user: DisplayUser) {
console.log(`${user.name}(${user.email})`);
}
Pickのようなユーティリティ型を使えば、巨大な型をすべて手書きしなくても、必要な部分だけを安全に扱えます。
「型を書くのが大変=any」ではなく、「型を書くのが大変=ユーティリティ型で楽をする」という発想の転換が、any卒業の近道です。
どうしてもanyが必要なときの付き合い方
anyを使う場所を限定し、コメントを残す
移行期のコードや、型定義が存在しない古いライブラリを扱う場合など、現実的にanyを使わざるを得ない場面もあります。
そうした場合は、anyを使ったことと理由を明示しておくことが大切です。
// TODO: 型定義が提供されていない旧ライブラリのため、暫定的にanyを使用
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function legacyPluginInit(config: any) {
// ...
}
コメントを残しておくことで、後から見た自分や他のメンバーが「意図的にanyにしている箇所」だと判断でき、無闇にanyが広がっていくのを防げます。
ESLintでanyの使用を検知する
@typescript-eslint/no-explicit-anyというESLintルールを使うと、コード中のanyをチーム全体で可視化できます。
{
"rules": {
"@typescript-eslint/no-explicit-any": "warn"
}
}
いきなりerrorにすると開発が止まってしまう場合は、まずwarnにしておき、目につく状態を作ることから始めるのがおすすめです。
「anyが悪いのではなく、anyが野放しになっている状態が悪い」という捉え方をすると、無理なく改善を進められます。
まとめ
この記事のポイント
anyは型チェックを無効化する特別な型で、多用するとTypeScriptの恩恵が失われる- 型が分からない値には
anyではなく、使用前に確認を強制するunknownを検討する - 型を保ったまま柔軟に扱いたい場合はジェネリクスが有効
- 複雑な外部データの型には
Pickなどのユーティリティ型で必要な部分だけを切り出す - どうしても
anyが必要な場合は、コメントとESLintルールで使用箇所を可視化する
次に読むべき記事
anyから卒業できたら、次は外部ライブラリを使うときに欠かせない型定義(@types)の扱い方を見ていきましょう。
→ 次の記事:外部ライブラリの型定義(@types)の探し方・使い方