「型エラー、正直こわいです…」
「エラー文、何言ってるか全然わからないです…」
「もういいや、とりあえず any で通します…」
こんな気持ち、ありませんか?
TypeScriptを書いているのに、型がストレスになっている人はめちゃくちゃ多いです。でも安心してください。型は“才能”ではなく、練習の型(やり方)を知ればちゃんと書けるようになります。
この記事では、実務でよくあるケースをもとにした「プロンプト形式の練習問題」を用意しています。
初級(型の形に慣れる)→ 中級(unionと型ガード)→ 実務レベル(ジェネリクス)という順番で進めば、any に逃げなくても戦えるようになります。
ただし注意です。
コピペして満足、はNGです。
まず自分で考える → そのあと改善例を見る この順番でいきましょう。

今日から一緒に、any 卒業していきましょう。
TypeScriptの「型がつらい」あるあると最短の進め方
「型がつらい…」と感じる理由、だいたい同じです。
でも実は、守るポイントはたった3つだけでOKです。
まずはここだけ:型で守りたい3つ
型で守るのは、この3つだけ覚えてください。
- 入力(受け取るもの)
- 関数の引数、フォーム入力、APIのリクエストなど
- 出力(返すもの)
- 関数の戻り値、APIレスポンス
- 分岐(場合分け)
- 成功か失敗か、ログイン済みか未ログインか、など
この3つを守るだけで、「どこから手をつけるの?」問題はかなり減ります。
anyが増える本当の理由(悪者じゃないが危険)
any が増えるのは、サボりではありません。
こんなときに、つい「とりあえず通す」ために使ってしまいます。
正直に言うと、any は悪者ではないです。
でも「全部OKです」と言ってしまうので、バグも一緒に通してしまうのが危険なんです。
例:
APIのレスポンスが{ name: string }のはずなのに、anyにしていたせいでuser.nmaeとタイポしても気づけない、みたいなことが起きます。
守ってくれるはずの型が、いない状態になります。
この記事の使い方:プロンプト→答え→振り返り
この記事では、ただ説明するだけではありません。
毎回この流れでいきます。
- お題を見る
- まず自分で型を書く
- NG例(any逃げ)を見る
- 改善例を見る
- 「なぜそうなるか」を確認する
この順番が超大事です。
「読むだけ」では型は強くなりません。
でも「1問でも自分で考える」と、一気に理解が進みます。
まずは、入力・出力・分岐の3つを守る。

これだけ意識して、次の章から一緒に筋トレしていきましょう。
3分で整理!interface / type の使い分け
「interfaceとtype、結局どっち使えばいいんですか…?」
ここで毎回止まってしまう人、かなり多いです。
でも大丈夫です。今日は迷わない決め方を渡します。
まずは小学生でもわかる言い方でいきます。
これだけでだいぶスッキリします。
結論:迷ったらこう決める(早見表)
まずは決め打ちルールです。
| やりたいこと | 使うのは? | ひとこと理由 |
|---|---|---|
| オブジェクトの形を作る(APIデータなど) | interface | 後から拡張しやすい |
| 型を組み合わせたい(A | B など) | type | unionが得意 |
| 交差させたい(A & B) | type | 合体が得意 |
| 既存の型を広げたい | interface | extendsが自然 |
| 文字列の候補を限定したい | type | literal型が書きやすい |
迷ったらこうです。
まずはこれでOKです。100点を目指さなくていいです。
実務でよくある「型の作り方」パターン
実務だと、こんな場面がよく出ます。
① APIレスポンス
interface User {
id: number
name: string
email: string
}
→ データの「設計図」なのでinterfaceが自然です。
② ReactのProps
type ButtonProps = {
label: string
onClick: () => void
}
→ これはinterfaceでもOKです。
でも他の型と合体(&)する可能性があるならtypeが便利です。
③ 状態を表す型
type Status = "loading" | "success" | "error"
→ これはtype一択です。
interfaceでは書けません。
よくあるミス:差分が出る場面(拡張・合成)
ここがハマりどころです。
① interfaceは同じ名前だと“合体”する
interface User {
name: string
}
interface User {
age: number
}
→ これ、エラーにならずに合体します。
知らないと「なんで?」ってなります。
② typeは同名で作れない
type User = {
name: string
}
type User = {
age: number
}
→ これはエラーになります。
③ readonly や optional の付け忘れ
interface User {
name: string
age?: number
readonly id: number
}
? と readonly を忘れると、
「更新できないはずなのに更新できる」みたいな事故が起きます。
ここまでで大事なのはこれだけです。
まずは迷わず書くこと。
細かい違いは、使いながら覚えれば大丈夫です。

次は、実際に「型の形」をガンガン書いていきます。筋トレ開始です。
初級プロンプト:まずは「型の形」に慣れる
ここは型の筋トレゾーンです。
むずかしいことはやりません。
やることは毎回同じです。
🧭 型を作るミニ手順(毎回これ!)
- キー(項目名)を全部見る
- それぞれの型を決める(string?number?)
- 任意かどうかを決める(
?いる?) - ネストや配列があるかを見る
これを“機械みたいに”やるだけでOKです。
プロンプト① APIレスポンスの基本型(配列・ネスト)
1) 目的
APIの「配列+ネスト構造」を安全に書けるようになることです。
2) お題
ブログ記事一覧APIが次の形で返ってきます。
[
{
"id": 1,
"title": "TypeScript入門",
"author": {
"name": "Taro",
"email": "taro@example.com"
}
}
]
3) NG例(any逃げ)
const posts: any = fetchPosts()
→ これだと post.auther.name と打ち間違えても気づけません。
4) 改善例(型安全)
interface Author {
name: string
email: string
}
interface Post {
id: number
title: string
author: Author
}
const posts: Post[] = fetchPosts()
5) コピペ用ChatGPTプロンプト
あなたはTypeScriptの先生です。
次のお題に対して、
(1)目的
(2)NG例(any使用)
(3)改善例(型安全)
(4)なぜそうするか
を順番に説明してください。
最後に、似た練習問題を1つ追加してください。
お題:ブログ記事一覧APIの型定義(配列+ネスト)
プロンプト② フォーム入力の型(必須/任意、null)
1) 目的
「必須」と「任意」をちゃんと分けられるようになることです。
2) お題
ユーザー登録フォーム:
3) NG例
type Form = any
→ 何が必須か全然わかりません。
4) 改善例
interface RegisterForm {
name: string
email: string
age?: number
bio: string | null
}
ここで大事なのは:
違いを意識してください。
5) コピペ用ChatGPTプロンプト
あなたはTypeScriptの先生です。
次のお題に対して、
(1)目的
(2)NG例(any使用)
(3)改善例(型安全)
(4)なぜそうするか
を順番に説明してください。
最後に、似た練習問題を1つ追加してください。
お題:ユーザー登録フォームの型定義(必須/任意/nullあり)
プロンプト③ union型の基本(状態を表す型)
1) 目的
「どれか1つ」を型で表せるようになることです。
2) お題
画面の状態は次の3つだけです。
3) NG例
let status: any = "loading"
→ "done" と書いても止まりません。
4) 改善例
type Status = "loading" | "success" | "error"
let status: Status = "loading"
これで "done" はエラーになります。
型がちゃんと守ってくれます。
5) コピペ用ChatGPTプロンプト
あなたはTypeScriptの先生です。
次のお題に対して、
(1)目的
(2)NG例(any使用)
(3)改善例(型安全)
(4)なぜそうするか
を順番に説明してください。
最後に、似た練習問題を1つ追加してください。
お題:画面状態を表すunion型
プロンプト④ anyを使うと壊れる例
1) 目的
「anyがなぜ危ないか」を体感することです。
2) お題
次の関数で、ユーザー名を表示します。
function printUser(user: any) {
console.log(user.name.toUpperCase())
}
3) NGポイント
user = {} を渡してもコンパイルが通ります。
でも実行時にエラーになります。
4) 改善例
interface User {
name: string
}
function printUser(user: User) {
console.log(user.name.toUpperCase())
}
これで「nameがない」データは止められます。
5) コピペ用ChatGPTプロンプト
あなたはTypeScriptの先生です。
次のお題に対して、
(1)目的
(2)NG例(any使用)
(3)改善例(型安全)
(4)なぜそうするか
を順番に説明してください。
最後に、似た練習問題を1つ追加してください。
お題:anyを使った関数の危険性
ここまでできたら、
「型の形」はかなり慣れてきます。

次はもう一段レベルアップします。
union × 型ガードで、実務っぽくしていきます。
中級プロンプト:union × 型ガードで実務っぽくする
ここから一気に“現場感”が出ます。
ポイントはこれです。
unionは「どれか1つ」
型ガードで「これだ!」に絞る
この“絞る感覚”が身につけば、型エラーは怖くなくなります。
型ガードの型(in / typeof / instanceof)
まずは超基本の3つです。
例です。
type Result =
| { success: true; data: string }
| { success: false; error: string }
function handle(result: Result) {
if ("data" in result) {
console.log(result.data)
} else {
console.log(result.error)
}
}
Result は「どれか1つ」ですが、"data" in result で 成功パターンに絞れます。
これが型ガードです。
APIの成功・失敗レスポンスを安全に分ける
1) 目的
成功と失敗を安全に分けられるようになることです。
2) お題
APIが次のどちらかを返します。
3) NG例
type ApiResponse = any
→ response.data と書いても止まりません。
失敗時にクラッシュします。
4) 改善例
interface User {
id: number
name: string
}
type ApiResponse =
| { ok: true; data: User }
| { ok: false; message: string }
function handle(res: ApiResponse) {
if (res.ok) {
console.log(res.data.name)
} else {
console.log(res.message)
}
}
res.ok を見るだけで、
TypeScriptが中身を自動で絞ってくれます。
これが「安全に分岐する」感覚です。
「取りうる値」を狭める設計(literal型)
たとえば権限。
type Role = "admin" | "editor" | "viewer"
これにすると、
let role: Role = "superuser" // ❌ エラー
勝手な値を止められます。
「文字列だけど自由ではない」
これがliteral型の強みです。
エラー文の読み方テンプレ(どこを見る?)
エラー文、怖いですよね。でも見る場所は固定です。
🧠 エラー文テンプレ読み
1行目:何がダメ?
→ 「Type ‘A’ is not assignable to type ‘B’」
次:どこで?
→ この変数?この引数?
最後:
→ 期待してる型(B)
→ 実際の型(A)
例:
Type 'string' is not assignable to type 'number'
訳すと:
「stringはnumberに入れられませんよ」
それだけです。
英語の長さにビビらなくて大丈夫です。

次はいよいよラスボスです。
ジェネリクスを“便利な道具”にしていきます。
実務レベル:ジェネリクスを「便利な道具」にする
ここ、いちばん苦手な人が多いところです。
「って何ですか…?」
「なんか急にむずかしいです…」
大丈夫です。覚えることは1つだけです。
共通化したいけど、型はちゃんと残したい
それがジェネリクスです。
ジェネリクスは「型の引数」:まず1パターン覚える
まずはこれだけ覚えてください。
function identity<T>(value: T): T {
return value
}
意味はシンプルです。
identity<string>("hello")
identity<number>(123)
これで「型を保ったまま共通化」できます。
まずは
Tを1つだけで始めてください。
増やすのは必要になってからでOKです。
共通関数(fetcher / mapper / cache)課題
ここから実務っぽくいきます。
課題① fetcherを型安全にする
1) 目的
APIごとに違う型を、安全に受け取れるようにすることです。
2) NG例(any逃げ)
async function fetcher(url: string): Promise<any> {
const res = await fetch(url)
return res.json()
}
→ 何が返ってくるかわかりません。
3) 改善例(ジェネリクス使用)
async function fetcher<T>(url: string): Promise<T> {
const res = await fetch(url)
return res.json()
}
使う側:
interface User {
id: number
name: string
}
const user = await fetcher<User>("/api/user")
これで user.name は安全に使えます。
課題② mapper関数
配列を変換する関数です。
NG例
function mapData(data: any[], fn: any) {
return data.map(fn)
}
全部ゆるゆるです。
改善例
function mapData<T, U>(data: T[], fn: (item: T) => U): U[] {
return data.map(fn)
}
ここで初めてTとUの2つを使いました。
でもルールは同じです。
意味があるときだけ増やす
これがコツです。
制約(extends)とデフォルト型(=)の使いどころ
extendsは「安全ベルト」です。
例:
function getId<T extends { id: number }>(item: T) {
return item.id
}
「idを持っている型だけ使えますよ」という制限です。
これを入れないと、
getId({ name: "Taro" }) // ❌
みたいな事故が起きます。
デフォルト型もあります。
function createArray<T = string>(value: T): T[] {
return [value]
}
Tを指定しなければstringになります。
でも、最初は無理に使わなくて大丈夫です。
罠:Tを増やしすぎる/anyに戻る瞬間
よくある失敗です。
これは「設計が複雑すぎる」サインです。
対策はシンプルです。
- まずT1つで考える
- 本当に別の型が必要か考える
- それでも無理なら関数を分ける
ジェネリクスは魔法ではありません。
共通化の道具です。
ここまで来たら、かなり強いです。

次は最後です。
環境を整えて、「any卒業」を固定化します。
すぐ使える練習環境・教材と、any卒業チェックリスト
ここまで読んで「なるほど…でも続くかな?」と思っていませんか?
大丈夫です。大事なのは環境を固定することです。
やる場所が決まっていれば、迷いません。
練習環境:Playground / ローカル最小構成
まずはこれでOKです。
🌐 TypeScript Playground(無料・ブラウザ完結)
「1問だけ解く」には最強です。
💻 ローカル最小構成(慣れてきたら)
npm init -ynpm install typescript --save-devnpx tsc --init
あとは .ts ファイルを作って書くだけです。
最初はフレームワーク不要です。
純粋な型練習だけで十分です。
実務で効く道具:lint・型検査・スキーマ検証
実務では「自分の意思」より「仕組み」が大事です。
🛑 ESLint(TypeScript対応)
「気合で守る」は無理です。ツールで止めましょう。
🤖 型チェックをCIで自動化
tsc --noEmit
PR時に型エラーを落とすだけで、
チームの安全度が一気に上がります。
🧪 スキーマ検証(例:Zod)
外から来るデータ(APIなど)は信用できません。
たとえば
Zod
のようなスキーマ検証ライブラリを使うと、
という「入り口で守る」設計ができます。
型は“出口”だけでなく、入口も守ると強いです。
付録:✅ any卒業チェックリスト(今日から使える)
anyを足す前に、これを順番に試してください。
型はセンスではありません。仕組みと反復です。
環境を決めて、
チェックリストを横に置いて、
1日1問でもいいので続けてください。

最後に、次にやることを3つにまとめます。
まとめ(次にやること3つ)
ここまで読んでくださったあなたは、もう「型が怖い人」ではありません。
あとは手を動かすだけです。
やることは、たった3つに絞りましょう。
✅ 今日やること(10分でOK)
初級プロンプトを2問だけ解いてください。
必ず「自分で書く → 改善例を見る」の順番です。
any を使いそうになったら、チェックリストを横に置きましょう。
✅ 今週やること
中級(union × 型ガード)を1テーマ通しでやってください。
「分岐を安全にする」感覚がつけば、一気に実務っぽくなります。
✅ 来週やること
ジェネリクス課題を1本だけちゃんとやりましょう。
Tを1つから始めて、「共通化したいけど型は残したい?」を意識してください。
型はセンスではありません。反復と仕組みです。
any を減らすたびに、あなたのコードはちゃんと強くなっています。

今日から少しずつ、any卒業していきましょう。
よくある質問
- Q正直、anyってそんなにダメなんですか?
- A
ダメというより、最後の手段です。
anyは「全部OKです」と言ってしまう型なので、
タイポや想定外の値もスルーしてしまいます。まずはチェックリストの順番で考えてください。
どうしても今すぐ直せないときは、
// TODO: 型をあとで厳密にすると期限付きで残すのが現実的です。
- Qinterfaceとtype、結局どっちに統一すればいいですか?
- A
チームルールがあるならそれに従うのが最優先です。
個人なら、まずはこれでOKです。
完璧な使い分けを目指すより、
迷って手が止まらない状態をなくすことが大事です。
- Q型エラーが英語で読めません…
- A
見る場所は3つだけです。
- 何がダメ?(assignableって書いてある?)
- どこで?(この変数?引数?)
- 期待している型は?/実際の型は?
たとえば:
Type 'string' is not assignable to type 'number'
これは
stringはnumberに入れられませんよ
と言っているだけです。
長く見えるだけで、言ってることはシンプルです。
- Qジェネリクス、毎回わからなくなります…
- A
最初はこれだけでいいです。
function identity<T>(value: T): T
「入れた型を、そのまま返す」
これが理解できればOKです。
難しく感じたら、
「共通化したいけど型は残したい?」と自分に聞いてください。
YESならジェネリクスの出番です。
- Q実務で型を書く時間がありません…
- A
めちゃくちゃわかります。
でも実は、型を書く時間は
バグ調査の時間を減らします。こういう事故を早めに止めてくれます。
全部を完璧にしなくていいです。
まずは
この3つだけ守るところから始めてください。
それだけでも、
anyはかなり減ります。
