【TypeScript】「とりあえずany」から卒業するための考え方

JavaScript

こんにちは、かつコーチです。

前回は、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だと、それを受け取ったuseranyになり、そこから先のすべてのコードで型チェックが効かなくなります。

私も過去に、急いでいるときに「とりあえず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);
}

unknownanyと同じく「どんな値でも入る」型ですが、決定的に違うのは、中身を使う前に型を確認しないとエラーになるという点です。

「何でも受け取れるけど、使うときは必ず身元確認をさせる」というイメージで捉えると分かりやすいです。

外部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)の探し方・使い方

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