TypeScriptを書き始めたころ、僕はとりあえず any を置いて先に進んでいました。エラーが消えるからです。
でも実務でチーム開発をするようになって分かりました。any は「今すぐ楽になる代わりに、3ヶ月後の自分を殴る」書き方だということに。
この記事では、TypeScriptの基本的な型を、実務で使う頻度が高い順に12個整理します。単に型の一覧を並べるのではなく、「どういうときにどれを選ぶか」まで書きました。
読み終わるころには、any を使わずに書ける場面が確実に増えているはずです。
この記事でわかること
- 実務で使う基本の型12種類と、その書き方
- enum を今から新規で使うべきではない、本当の理由
- any と unknown の決定的な違い
- 「型を書かない」という選択が正解になる場面
- TypeScript 6.0 で strict がデフォルトになった話
そもそもTypeScriptの「型」とは何か
30秒で説明します。JavaScriptはこう書けてしまいます。
let name = 'Gorira';
name = 100; // エラーにならない
文字列を入れた変数に、あとから数値を入れられてしまう。実行するまで誰も気づきません。
TypeScriptはこれを、書いている最中に止めてくれます。
let name: string = 'Gorira';
name = 100; // ❌ Type 'number' is not assignable to type 'string'.
型とは「この変数にはこの種類の値しか入れません」という、コンパイラとの約束です。この約束があるおかげで、エディタの補完が効き、リファガリングが怖くなくなります。
この記事では var を使いません
現在のJavaScript/TypeScriptで var を使う理由はぼぼありません。再代入しないなら const、するなら let が基本です。
レベル1:まず覚える基本の型(6つ)
① 文字列型(string)
const animalName: string = 'Gorira';
const message: string = `こんにちは、${animalName}さん`;
② 数値型(number)
const age: number = 18;
const height: number = 175.5;
const hex: number = 0xff;
TypeScriptには int や float の区別がありません。整数も小数も、まとめて number です。
なお、桁の大きな整数を扱うときだけ bigint という別の型があります(const big: bigint = 9007199254740993n;)。実務で使う場面は少ないので、頭の隣に置いておく程度で十分です。
③ 真偽値型(boolean)
const isOpen: boolean = true;
const hasBanana: boolean = false;
④ 配列型(array)
const keywords: string[] = ['Gorira', 'Goriko', 'Gorio'];
この配列に 0 のような数値を入れると警告が出ます。文字列しか入れないと宣言しているからです。
複数の型を許可したい場合は、パイプ | でつなぎます。
const mixed: (string | number)[] = ['Gorira', 'Goriko', 0];
カッコの位置に注意してください。 (string | number)[] と string | number[] は意味が全く違います。
const a: (string | number)[] = ['Gorira', 0]; // ✅ 文字列と数値が混ざった配列
const b: string | number[] = 'Gorira'; // ⚠️「文字列」または「数値の配列」
b の書き方だと、文字列そのものが代入できてしまいます。意図しない挙動の原因になりやすいので、迷ったらカッコをつけましょう。
また、Array<string> というジェリクリス記法も同じ意味です。チームのコーディング規約に合わせれば問題ありません。
const keywords: Array<string> = ['Gorira', 'Goriko'];
⑤ タプル型(tuple)
配列の「何番目に何の型が入るか」まで固定する型です。
const payment: [number, string, number] = [1, 'Gorira', 65];
payment[0]; // number として扱われる
payment[1]; // string として扱われる
[‘Gorira’, ‘Gorira’, 65] のように順番が違うと警告が出ます。
実務での出番はどこか。 一番よく見るのはReactの useState です。
const [count, setCount] = useState<number>(0);
// 戻り値の型は [number, Dispatch<SetStateAction<number>>] というタプル
自分でタプルを定義する機会は多くありませんが、「1番目が値、 2番目が更新関数」のような位置に意味がある戻り値を返すときに使います。
要素に名前をつけることもできます。エディタの表示が分かりやすくなります。
const point: [x: number, y: number] = [10, 20];
React + TypeScript でコンポーネントの Props に型をつける方法は、React TypeScriptでのPropsの基本的な使い方 で解説しています。
⑥ オブジェクト型(object)
まず、キーが決まっているオブジェクト。これが一番よく使います。
const gorilla: { name: string; id: number } = {
name: 'Gorira',
id: 1,
};
次に、キーが動的に増えるオブジェクト。インデレカシシグナチャといいます。
const dictionary: { [key: string]: string } = {
name: 'Gorira',
species: 'inu',
};
dictionary.color = 'black'; // ✅ あとから追加できる
キーが何になるか分からない、いわゆる辞書的なデータに使います。組み込みの Record を使うと、同じことがもう少し読みやすく書けます。
const dictionary: Record<string, string> = {
name: 'Gorira',
};
ただしインデレカシシグナチャはタイプミスを検出できなくなるという副作用があります。キーが決まっているなら、必ず1つ目の書き方を選んでください。
レベル2:組み合わせて使う型(3つ)
⑦ ユニオン型(union)
「AまたはB」を表す型です。TypeScriptで最も使う型と言ってもいいでしょう。
let sample: string | number;
sample = 'Gorira'; // ✅
sample = 56; // ✅
sample = true; // ❌ boolean は許可していない
boolean も入れたいなら、そのまま足します。
let sample: string | number | boolean;
ユニオン型を受け取ったら、絞り込みが必要になります。 型が確定していない状況では、その型固有のメソッドが使えないからです。
function printId(id: string | number) {
// id.toUpperCase() ← ここではまだ呼べない
if (typeof id === 'string') {
console.log(id.toUpperCase()); // ✅ ここでは string と確定している
} else {
console.log(id.toFixed(0)); // ✅ ここでは number と確定している
}
}
この typeof によるチェックを型の絞り込み(Narrowing)と呼びます。ユニオン型とセットで必ず覚えることになります。
⑧ リテレル型
「特定の値だけ」を型にできます。これがTypeScriptの強力なところです。
let status: 'draft' | 'published' | 'archived';
status = 'draft'; // ✅
status = 'deleted'; // ❌ 3つのうちどれかしか入らない
ステータス、権限、サイズ指定など、取りうる値が決まっているものはすべてリテレル型のユニオンで表現できます。文字列のタイプミスがコンパイル時に消えます。
⑨ enum型(と、それを使わない選択)
まず書き方から。
enum Animals {
Gorira = 100,
Goriko = 3,
Gorio = 50,
}
console.log(Animals.Gorira); // 100
値を省略すると、0から自動で連番が振られます。
enum Ningen {
Gorira, // 0
Goriko, // 1
Gorio, // 2
}
ここまでが従来の解説です。ここからが本題で、新規プロジェクトで enum を選ぶ理由は、今ではほとんどありません。
先に、よくある誤解を1つ潟しておきます。「数値 enum は定義していない数値も代入できてしまうから危険」という指摘をよく見かけますが、これはTypeScript 5.0 で解消済みです。 5.0 で全ての enum がユニオン enum として扱われるようになり、各メンバーに固有の型が割り当てられました。今は定義外の数値を渡すとちゃんとエラーになります。 4.9以前の情報を引きずっている記事が多いので注意してください。
では何が問題なのか。理由は2つあります。
理由1:enum はコンパイル後にJavaScriptのコードとして残ります。
他の型はすべて消えるのに、enum だけがランタイムのオブジェクトを生成します。実際、こんなコードに変換されます。
// 元のコード
enum Result {
Ok = 'Ok',
Error = 'Error',
}
// 変換後のJavaScript
var Result;
(function (Result) {
Result["Ok"] = "Ok";
Result["Error"] = "Error";
})(Result || (Result = {}));
型のつもりで書いたものが、バンドルサイズに乗ってきます。
理由2:型だけを剥がして実行する環境で動きません。
Node.js は今、TypeScriptファイルを直接実行できます。ただしこれは「型注釈を空白に置き換えて実行する」だけの仏組み(型ストリッピング)なので、コード生成が必要な enum は対象外です。使うには --experimental-transform-types フラグが要ります。
この流れを受けて、TypeScript 5.8 では erasableSyntaxOnly というフラグが追加されました。有効にすると、enum・namespace・パラメータプロパティといった「消せない構文」がコンパイルエラーになります。
// tsconfig.json
{
"compilerOptions": {
"erasableSyntaxOnly": true
}
}
// ❌ error: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.
enum Direction {
Up,
Down,
}
代替は as const を使ったオブジェクトです。
const Animals = {
Gorira: 100,
Goriko: 3,
Gorio: 50,
} as const;
type Animal = typeof Animals[keyof typeof Animals]; // 100 | 3 | 50
console.log(Animals.Gorira); // 100
書き方は少し長いですが、挙動は素直で、リテレル型の恩惠をそのまま受けられます。単に選択肢を表現したいだけなら、リテレル型のユニオン(’draft’ | ‘published’)で十分なことも多いです。
既存コードの enum を今すぐ書き換える必要はありません。
動いているものは動かしておいて、新しく書くときの選択肢として覚えておけば十分です。
レベル3:注意して使う型(3つ)
⑩ any型 —— 使わないための型
let something: any = 'foo';
something = 100;
something = true;
something = { id: 1 };
何でも入ります。そして何をしても怒られません。
const value: any = 'Gorira';
value.thisMethodDoesNotExist(); // 警告なし。実行時にクラッシュする
any を書いた瞬間、その変数まわりのTypeScriptの恩惠はゼロになります。エディタの補完も効きません。
使っていい場面は、正直ほとんどありません。 JSからの移行中の一時的な逃げ道か、型定義のないライブラリを触るときくらいです。それも、あとで消す前提で // TODO: コメントを添えておきたいところです。
⑪ unknown型 —— anyの安全な代替
「型が分からない」を表す点は any と同じですが、使う前に型を確かめることを強制されます。
const value: unknown = getDataFromApi();
value.toUpperCase(); // ❌ まず型を確認しろ、と怒られる
if (typeof value === 'string') {
value.toUpperCase(); // ✅ string と確認できたので使える
}
APIのレスガスヽンや JSON.parse() の戻り値など、外から来る素性の分からないデータにはこれを使います。
catch で受け取るエラーも同じです。strict が有効なら、これは unknown 型になります。
try {
// 何か処理
} catch (error) {
// error は unknown 型
if (error instanceof Error) {
console.log(error.message); // ✅
}
}
「any と書きたくなったら、まず unknown で置き換えられないか考える」。これだけで型安全性がかなり上がります。
⑫ null と undefined
const el: Element | null = document.querySelector('#app');
querySelector は要素が見つからなければ null を返します。だから戻り値は Element | null になります。
strictNullChecks が有効だと、null の可能性があるものをそのまま使えません。
el.classList.add('active'); // ❌ 'el' is possibly 'null'.
// 対処1:チェックする(推奨)
if (el) {
el.classList.add('active');
}
// 対処2:オプショナルチェーン
el?.classList.add('active');
面倒に感じるかもしれませんが、これは「実行時のTypeErrorを事前に潰している」ということです。 JavaScriptで一番よく踏むバグを、コンパイラが代わりに見つけてくれています。
なお el! のような非 null アサージョン(!)で黩らせることもできますが、これは「絶対にnullじゃないと僕が保証する」という宣言なので、使うなら根拠があるときだけにしましょう。
型に名前をつける:type と interface
同じ型を何度も書くのは面倒です。名前をつけて使い回せます。
type Gorilla = {
name: string;
age: number;
};
const gorira: Gorilla = { name: 'Gorira', age: 18 };
const goriko: Gorilla = { name: 'Goriko', age: 3 };
interface でもほぼ同じことができます。
interface Gorilla {
name: string;
age: number;
}
どちらを使うべきか。 実務での判断基準はシンプルです。
| やりたいこと | type | interface |
|---|---|---|
| オブジェクトの形を定義 | ✅ | ✅ |
| ユニオン型に名前をつける | ✅ | ❌ |
| 同名で宣言をマージ | ❌ | ✅ |
| 拡張のしかた | & で交差 | extends |
迷ったら type を使えば困りません。 type はユニオン型やプリミティブにも名前をつけられるので、単純に守備範囲が広いです。ライブラリの型を外から拡張したい(宣言マージしたい)ときだけ interface が必要になります。
チーム開発では規約で統一されていることが多いので、まずはプロジェクトの既存コードに合わせましょう。
実際のコンポーネントで型を使う手順は React TypeScriptでのPropsの基本的な使い方 にまとめてあります。
オプショナルプロパティとreadonly
「あってもなくてもいいプロパティ」は ? をつけます。
type Gorilla = {
name: string;
nickname?: string; // 省略可能
};
const gorira: Gorilla = { name: 'Gorira' }; // ✅ nickname がなくてもOK
? をつけたプロパティは string | undefined として扱われるので、使うときは存在チェックが要ります。
console.log(gorira.nickname?.toUpperCase());
再代入させたくないプロパティには readonly をつけます。
type Config = {
readonly apiUrl: string;
};
const config: Config = { apiUrl: 'https://example.com' };
config.apiUrl = 'https://evil.com'; // ❌ 読み取り専用
型を書かない、という選択(型推論)
ここまで型の書き方を並べてきましたが、実は明示的に書かないほうがいい場面も多いです。
let unknownVar = 'gorigorigorira'; // この時点で string 型と推論されている
unknownVar = 10; // ❌ 数値は代入できない
: string と書いていないのに、TypeScriptが初期値から型を判断してくれています。これを型推論といいます。
実務では、こう使い分けるのが読みやすいです。
// ❌ 冗長。見れば string だと分かる
const name: string = 'Gorira';
// ✅ 推論に任せる
const name = 'Gorira';
// ✅ 関数の引き取りと戻り値は明示する
function greet(name: string): string {
return `Hello, ${name}`;
}
目安は「関数の境界には型を書き、変数の中身は推論に任せる」。 引き取りと戻り値に型があれば、その関数を使う側は中身を読まずに済みます。逆に、初期値から明らかな変数にまで型を書くと、ノイズが増えるだけです。
strictはTypeScript 6.0からデフォルトで有効になった
ここまで strictNullChecks や unknown の話が出てきましたが、これらは tsconfig.json の strict オプションで一括して有効になります。strict は単独のフラグではなく、複数の厳格化オプションをまとめて有効にするメタフラグです。
- noImplicitAny:型を書き忘れたときに暗黙の any にしない
- strictNullChecks:null と undefined を独立した型として扱う
- useUnknownInCatchVariables:catch の変数を unknown にする
- ほか、strictFunctionTypes、strictPropertyInitialization など
そして重要なのがここです。 2026年3月にリリースされた TypeScript 6.0 から、strict のデフォルト値が true になりました。
つまり「厳格にするために有効化する」のではなく、「緩くしたいなら明示的に false を書く」時代になったということです。既に "strict": true を書いていた人には影響ありませんが、デフォルトの false に依存していたプロジェクトは、6.0 に上げた瞬間に大量のエラーが出ます。
ちなみに 6.0 では module のデフォルトが esnext、target のデフォルトが es2025 にも変わっています。そして 6.0 は JavaScript ベースの最後のリリースで、2026年7月に出た TypeScript 7.0 では Go で書き直されたコンパイラになり、フルビレドが約8〜12倍高速化しました。
これから学ぶ人にとっては、最初から厳格なTypeScriptが標準になったということです。緩い書き方を覚え直す必要がないのは、もしろラキーだと思います。
実務での使い分けチートシート
| やりたいこと | 使う型 |
|---|---|
| 文字・数値・真偽値 | string / number / boolean |
| 同じ型の値のリスト | string[] |
| 複数の型が混ざるリスト | (string | number)[] |
| 位置に意味がある固定長の配列 | [number, string] |
| キーが決まったオブジェクト | { name: string; id: number } |
| キーが動的なオブジェクト | Record<string, string> |
| AまたはB | string | number |
| 決まった値のどれか | ‘draft’ | ‘published’ |
| 選択肢の定数セット | as const オブジェクト(enumより推奨) |
| 型に名前をつけて再利用 | type |
| ライブラリの型を拡張 | interface |
| あってもなくてもいい項目 | nickname?: string |
| 書き換え禁止 | readonly |
| 型が分からない外部データ | unknown |
| 型を諾める | any(原則使わない) |
よくあるエラーと対処法
Type 'string' is not assignable to type 'number'.
宣言した型と違う値を入れています。一番シンプルなエラーです。型宣言か代入する値、どちらかを直しましょう。
Object is possibly 'null'.
null になりうる値をチェックせずに使っています。if で存在確認するか、オプショナルチェーン ?. を使ってください。
Property 'xxx' does not exist on type 'yyy'.
型定義にないプロパティにアクセスしています。型定義に足りないプロパティがあるか、タイプミスかのどちらかです。まずタイプミスを疑いましょう。
Parameter 'xxx' implicitly has an 'any' type.
関数の引き取りに型を書いていません。引き取りに型注釈をつければ消えます。TypeScript 6.0 以降はデフォルトで出るようになったエラーです。
This syntax is not allowed when 'erasableSyntaxOnly' is enabled.
enum や namespace を使っています。as const オブジェクトかリテレル型のユニオンに書き換えてください。
よくある質問
Q. 型は毎回すべて書くべき?
いいえ。関数の引き取りと戻り値には書き、ローカル変数は推論に任せるのが読みやすいです。
Q. typeとinterface、結局どっち?
新規なら type で困りません。ライブラリの型を宣言マージで拡張したいときだけ interface が必要です。プロジェクトの規約があればそれが最優先です。
Q. anyを絶対に使ってはいけない?
絶対ではありませんが、書く前に unknown で代替できないか一度考えてください。多くの場合は unknown か具体的な型で置き換えられます。
Q. enumを使っている既存コードは書き換えるべき?
急いで書き換える必要はありません。動いているものは動かしておいて、新規に書くときだけ as const かリテレル型を選べば十分です。ただし Node.js で .ts を直接実行したい場合や、erasableSyntaxOnly を有効にしたい場合は書き換えが必要になります。
Q. どこまで覚えれば実務で書ける?
この記事のレベル1とレベル2、それに unknown を押さえていれば、フロントエンドの実装で困る場面はかなり減ります。ジェリクリスやユーティリティ型は、必要になったタイミングで調べれば間に合います。
Vue3 で TypeScript を使う場合は 【Vue3TypeScript】CompositionAPIでのComputedの使い方 も参考にしてください。
まとめ
TypeScriptの基本の型を、実務で使う頻度順に整理しました。
- レベル1:string number boolean 配列 タプル オブジェクト —— まずここ
- レベル2:ユニオン型 リテレル型 enum(とas const) —— ここまでで実務の8割
- レベル3:any は避け、unknown で受けて絞り込む
最初から完璧に型をつけようとすると手が止まります。TypeScript 6.0 以降は strict がデフォルトで有効なので、エディタが赤線を引いたところだけ直していけばいい。それを繰り返しているうちに、自然と型が身についてきます。
僕自身、最初は any だらけでした。それでいいと思います。大事なのは、any を書いたときに「あとで直す」と思えることです。
参考リンク
- TypeScript公式ハンドブッキ – Everyday Types
- TypeScript公式 – TSConfig Reference
- Announcing TypeScript 6.0 – Microsoft DevBlogs
- Announcing TypeScript 5.8 Beta(erasableSyntaxOnly)
- Node.js公式ドキュメント – Modules: TypeScript
