TypeScript | 基礎文法:Union・基本型操作 – 「TypeScriptらしい書き方」の理解

TypeScript
スポンサーリンク

「TypeScriptらしい書き方」って何を指しているのか

まず前提をはっきりさせます。
「TypeScriptらしい書き方」というのは、
単に「: string とか : number をつけること」ではありません。

ざっくり言うと、

「自分が頭の中で思っている前提(状態・パターン・ありうる値・ありえない値)を、
型としてコードにちゃんと書ききること」

これが TypeScript らしさです。

JavaScript だと、頭の中の前提は「なんとなく」で済ませがちです。
TypeScript は、その「なんとなく」をちゃんと表現して、
型としてコンパイラに伝えることを求めてきます。

ここからは、
同じことをやっていても「JSっぽい書き方」と「TSらしい書き方」がどう違うのかを、
具体例を通して見ていきます。


1. 「とりあえず any」から「パターンを型で表す」へ

JS的な書き方:なんでも受ける関数

JavaScript 的な発想だと、こうなりがちです。

function handle(value: any) {
  if (typeof value === "string") {
    console.log(value.toUpperCase());
  } else {
    console.log(value);
  }
}
TypeScript

any は「TSを付けてるけど、中身は JS と同じでいいや」という逃げです。
この書き方だと、value に本来ありえないもの(配列・オブジェクトなど)も平気で通ってしまいます。

TypeScriptらしい書き方:union型で「候補」を絞る

function handle(value: string | number) {
  if (typeof value === "string") {
    console.log(value.toUpperCase());
  } else {
    console.log(value.toFixed(2));
  }
}
TypeScript

やっていることはほぼ同じですが、
ここでは「この関数は string と number だけを受け付ける」と宣言しています。

TypeScript 的には、

「この関数は“どんな値でも”扱う関数ではない」
「string と number だけの世界の話をしている」

と型で表現しているわけです。

重要なのは、「JS の書き方に型注釈を付ける」のではなく、
「そもそもこの関数は何のための関数なのか」を型に落とす というスタンスです。


2. 「if で頑張る」から「Discriminated Unionで状態を表す」へ

JS的な書き方:boolean や undefined でフラグ管理

よく見るのはこんな形です。

type State = {
  isLoading: boolean;
  data?: string;
  error?: string;
};

function render(state: State) {
  if (state.isLoading) {
    console.log("読み込み中");
  } else if (state.error) {
    console.log("エラー:", state.error);
  } else if (state.data) {
    console.log("成功:", state.data);
  } else {
    console.log("何もしていない");
  }
}
TypeScript

一見うまく動いているように見えますが、

isLoading が false で、data も error も undefined の状態がありうる
成功とエラーが同時にセットされてしまう可能性がある

など、「ありえてほしくない状態」を型が防げていません。

TypeScriptらしい書き方:状態を union で列挙する

type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; error: string };

function render(state: State) {
  switch (state.status) {
    case "idle":
      console.log("何もしていない");
      break;
    case "loading":
      console.log("読み込み中");
      break;
    case "success":
      console.log("成功:", state.data);
      break;
    case "error":
      console.log("エラー:", state.error);
      break;
  }
}
TypeScript

ここでは、

成功のときだけ data があり
エラーのときだけ error があり
両方同時には起こらない

という前提を、型そのものに刻み込んでいます。

これはまさに 「TypeScriptらしい」=「状態のバリエーションと、それぞれが持つべき情報を、Union とプロパティで表現する」 例です。


3. 「なんとなく optional」から「null / undefined をちゃんと扱う」へ

JS的なノリ:とりあえず ? を付けておく

type User = {
  name?: string;
};

function greet(user: User) {
  console.log("こんにちは、" + user.name?.toUpperCase());
}
TypeScript

動くには動きますが、

名前が無いときにどう振る舞いたいのか
それは仕様として正しいのか

が曖昧なままです。

TypeScriptらしい書き方:どこで「欠けている」を処理するか決める

欠けている可能性を認めるなら、その処理方針をどこかで決めます。

type RawUser = {
  name: string | null;
};

type User = {
  name: string;
};

function toUser(raw: RawUser): User {
  return {
    name: raw.name ?? "名無しさん",
  };
}

function greet(user: User) {
  console.log("こんにちは、" + user.name.toUpperCase());
}
TypeScript

ここでは、

外側の世界から来るときは「null かもしれない」
アプリの中では「必ず string として扱う」

という線引きを型でしています。

TypeScript らしいのは、
「null や undefined を“なんとなく” optional でぼかさず、どこで処理しきるかを決めて型にする」 ことです。


4. 「巨大な型をそのまま引き回す」から「必要な性質だけの型を引数にする」へ

JS的な書き方:なんでも渡して、なんでも見れる

type User = {
  id: number;
  name: string;
  email: string;
  age: number;
};

function sendMail(user: User) {
  console.log("送信先:", user.email);
}
TypeScript

sendMail は email しか使っていないのに、User 全体を要求しています。
これは JavaScript 的な「とりあえず全部渡しておく」設計です。

TypeScriptらしい書き方:その関数が「本当に必要とする情報」だけを要求する

type HasEmail = {
  email: string;
};

function sendMail(user: HasEmail) {
  console.log("送信先:", user.email);
}
TypeScript

こうすると、

この関数は email さえあれば動く
User 以外でも email を持っていれば使える

という設計上の意味が、型から見えるようになります。

TypeScript らしさは、
「関数の引数型が、その関数の責任範囲をきれいに表している」 ところに出ます。


5. 「とりあえず any / unknown」から「型で“何がしたいか”を伝える」へ

any で逃げたくなる場面ほど、TypeScriptらしさのチャンス

たとえば「API のレスポンス」を最初に扱うとき、
ついこう書きたくなります。

function handleResponse(res: any) {
  console.log(res.data);
}
TypeScript

でも、本当に「どんなレスポンスでもいい」わけではないはずです。

成功時は data があって
失敗時は error があって
どちらか一方だけのはず

と頭の中で思っているなら、それを型にします。

type SuccessResponse = {
  ok: true;
  data: string;
};

type ErrorResponse = {
  ok: false;
  error: string;
};

type ApiResponse = SuccessResponse | ErrorResponse;

function handleResponse(res: ApiResponse) {
  if (res.ok) {
    console.log(res.data);
  } else {
    console.error(res.error);
  }
}
TypeScript

やっていることは「if で分ける」だけですが、
ここでは 「成功 / 失敗のパターン / 両立しない性質」が、型としてコードに焼き付いている のがポイントです。

TypeScript らしい書き方は、
「とりあえず any にしておこう」を一歩我慢して、

この値をどういうパターンで扱いたいのか?
そのパターンごとに、何が必ずあって、何が絶対にないのか?

を丁寧に型として表現していくことです。


6. まとめ:「TypeScriptらしいか?」を判断する視点

最後に、判断基準をシンプルな問いに落とします。

その型は、「ありうる値」と「ありえない値」の境界をはっきり描いているか?
その関数の引数型は、「この関数が本当に何を前提としているか」をちゃんと表しているか?
状態やパターンが複数あるときに、「なんとなく if で頑張る」ではなく、「Union と判別プロパティ」で表そうとしているか?
null や undefined を、「あとでなんとかする」ではなく、「どこで処理しきるか」を意識しているか?

このあたりに「はい」と言えるコードは、
かなり TypeScript らしい書き方になっています。

TypeScript は、
JavaScript に「型をちょっと足したもの」ではなく、
あなたの頭の中の前提・ルール・場合分けを、ちゃんとコードとして残すための言語 です。

コードを書きながら、たまに立ち止まってこう自分に聞いてみてください。

「今の書き方、TypeScript に“自分の考えている前提”をちゃんと伝えてるかな?」

その問い自体が、「TypeScriptらしい書き方」を身につける一番の近道になります。

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