「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);
}
}
TypeScriptany は「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);
}
TypeScriptsendMail は 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らしい書き方」を身につける一番の近道になります。

