TypeScriptへの移行
この章では、TypeScriptへの移行について概要を解説し、ハンズオン形式で実際に動作させながら学習します。これにより、型注釈・interface・ユニオン型・ジェネリクスなど、JavaScriptコードをTypeScript化する基本が身につきます。
1. 本章の概要
1.1 本章の目的
前章までで扱ったJavaScriptには、型の仕組みがありません。規模の大きいフロントエンドアプリケーションになるほど、型がないことが実行時エラーやリファクタリング時の不具合につながりやすくなります。TypeScriptはJavaScriptに静的型付けを追加した言語で、Next.js/React開発では実質的な標準になっています。以降のフレームワーク章に入る前に、TypeScriptを使う理由とJavaScriptとの関係を押さえつつ、TypeScriptの基本文法を身につけます。
1.2 ハンズオンの流れ
セットアップで最小のTypeScript環境を作り、tsconfig.json の基本設定を確認したあと、以降の各節では、型注釈・interface・型エイリアス・ユニオン型・ジェネリクスといった主な型機能を1つずつ手元で書き、コンパイルと実行を通して動作を確かめます。
1.3 事前準備
必要なツール
この章では、以下のツールを使用します。まだインストールしていない場合は、リンク先の手順に沿って準備をお願いします。
| ツール名 | 関連箇所 | 理由 |
|---|---|---|
| Visual Studio Code | Visual Studio Codeのインストール | TypeScriptコードを記述するエディタとして使用する |
| Node.js | Node.jsをインストールしよう | tsc によるコンパイルと、コンパイル後のJavaScriptを実行するために使用する |
2. なぜTypeScriptを使うのか
前章で学んだJavaScriptには、コードを書く上で強力な柔軟さがある一方で、型がないことに起因する課題が数多くあります。関数に想定と違う型の値を渡してしまったり、オブジェクトのプロパティ名を打ち間違えたりしても、実行するまでエラーに気づけません。小さなスクリプトなら問題になりませんが、画面数が増えてコード量が数千行を超えるアプリケーションでは、こうしたケアレスミスが積み重なってバグの原因になります。
2.1 型がないことで起きる問題
型がないことで起きる問題を、ブラウザの開発者ツール(DevTools)のコンソールでJavaScriptコードを実際に動かして確認します。開発者ツールの開き方は JavaScriptの基本文法 の「開発環境の準備」節に記載があります。
数値と文字列の混同
数値を扱う関数に文字列を渡してしまったときの挙動を、double 関数を例に確認します。以下のコードをコンソールに貼り付けて実行します。
function double(x) {
return x * 2;
}
console.log(double("3"));
console.log(double("abc"));
このコードはエラーにならず、double("3") は 6 を、double("abc") は NaN(Not a Number)を出力します。
6
NaN
JavaScriptは * 演算子で暗黙の数値変換を試みるため、"3" のように数値として解釈できる文字列を渡した場合はたまたま 6 が返り、"abc" のように解釈できない文字列を渡した場合は NaN になります。呼び出し側が誤った型を渡していても、関数側では検知できず、返ってきた値を見るまで問題に気づけません。
📝 NaN(Not a Number)とは |
|---|
NaN は「数値として意味のある結果を出せなかった」ことを示すJavaScriptの特殊な値です。文字列 "abc" を数値として扱おうとしたり、0 / 0 のように数値化できない演算を行ったりしたときに現れます。型は number として扱われますが、NaN を含んだ計算の結果もふたたび NaN になるため、気づかないと後続の計算にも波及します。 |
プロパティ名の書き間違い
オブジェクトのプロパティ名を書き間違えたときの挙動を確認します。以下のコードをコンソールに貼り付けて実行します。
const user = { name: "田中太郎", email: "tanaka@example.com" };
console.log(user.emial);
email を emial と書き間違えていますが、JavaScriptは何も警告せず、undefined を返します。
undefined
こうしたミスは実行前には気づけず、実際にユーザが画面を操作したときに初めて発覚します。特にWebアプリケーションでは、ユーザがその機能を初めて操作したタイミングでしか問題が起きないため、あまり使われない機能に潜んでいると、リリースから数ヶ月から数年経って初めて発覚することもあります。
2.2 TypeScriptで解決できること
上のようなミスをコード実行前に検出したいときに、TypeScriptを使います。JavaScriptに型(type)の仕組みを加えた言語で、Microsoftが開発しているオープンソースの言語です。.ts ファイルに書いたコードを tsc(TypeScript Compiler)でJavaScriptに変換(コンパイル)してから実行します。
TypeScriptの実際の動作確認は、次の「セットアップ」節でTypeScriptプロジェクトを準備してから以降の各節で行います。ここでは、TypeScriptを使うとどんな挙動になるかの概要だけを押さえてください。
先ほどの double 関数を、引数と戻り値の型を明示した形でTypeScriptに書き換えると次のようになります。
function double(x: number): number {
return x * 2;
}
console.log(double("3"));
このコードをVisual Studio Codeで開くと、double("3") の部分に赤い波線が引かれ、以下のようなエラーが表示されます。
Argument of type 'string' is not assignable to parameter of type 'number'.
Argument of type 'string' is not assignable to parameter of type 'number'. は「文字列型の引数は数値型のパラメータに割り当てられない」という意味で、TypeScriptが呼び出し側が誤った型の値を渡していることをコンパイル時に検出できていることを示します。ブラウザで実行する前にミスに気づけるため、動かして初めてバグを見つける手戻りが減ります。
型注釈の具体的な書き方は「型注釈と型定義」節で扱います。
2.3 エディタでの補完とリファクタリング支援
TypeScriptのメリットは、実行前にエラーを検出できるだけではありません。型の情報があることで、Visual Studio Codeなどのエディタは以下のような支援を強化できます。
- オブジェクトのプロパティ名を途中まで入力すると、候補が正確に補完される
- 関数を呼び出すときに、引数の型と個数がヒントとして表示される
- 変数名を変更すると、参照箇所すべてを安全に一括で書き換えられる
- 未使用の変数や到達不能なコードを即座に指摘してくれる
型があることで、エディタは「この変数はどんな値を持ち得るか」を正確に把握できます。結果として、コードを読み書きするスピードそのものが上がります。
2.4 モダンなフロントエンド開発では標準
現在、Reactを使ったフロントエンド開発の多くはTypeScriptが標準として採用されています。次の章から学ぶNext.jsも、公式ドキュメントの例はTypeScriptで書かれています(TypeScript(Next.js公式ドキュメント)に「Next.js provides a TypeScript-first development experience for building your React application.」と記載があります)。
TypeScriptを別の言語として構えて学ぶ必要はありません。JavaScriptに型を加えたものという位置づけで、既存のJavaScriptの知識をそのまま活かせます。本章でも、if や for などJavaScriptで学んだ構文はそのまま使える前提として、TypeScriptがJavaScriptに追加する型の書き方に絞って解説します。
3. セットアップ
TypeScriptを使うには、typescript パッケージをインストールし、プロジェクトごとに tsconfig.json という設定ファイルを用意します。ここではまず最小構成のTypeScriptプロジェクトを準備し、続いて短いTypeScriptコードをコンパイル・実行して環境が正しく動いていることを確認します。
3.1 プロジェクトの初期化
まず、TypeScriptを扱うための空のプロジェクトを作り、typescript パッケージのインストールと tsconfig.json の生成までを行います。
任意の場所に ts-practice フォルダを作成し、Visual Studio Codeの「ファイル」→「フォルダーを開く」から、作成した ts-practice フォルダを開きます。以降の操作は、Visual Studio Codeのターミナルから行います。
ts-practice ← このフォルダを作成
以下のコマンドで、npm がインストールされていることを確認します。
npm --version
以下のように npm のバージョンが表示されれば、インストールは確認できています。
10.x.x
バージョンが表示されない場合は、Node.jsをインストールしよう を先に実施してください。
インストールが確認できたら、npm の初期化を行います。以下のコマンドを実行すると、package.json が生成されます。
npm init -y
次に、TypeScriptパッケージを開発用の依存関係としてインストールします。
npm install --save-dev typescript
インストールが完了したら、以下のコマンドで TypeScript がプロジェクトに正しく入っていることを確認します。
npx tsc --version
以下のように TypeScript のバージョンが表示されれば、インストールは確認できています。
Version 7.x.x
| ⚠️ 「Ok to proceed?」「This is not the tsc command」が出る場合 |
|---|
npx tsc --version を実行したときに Ok to proceed? や This is not the tsc command you are looking for のようなメッセージが表示される場合は、ローカルの TypeScript が見つかっていない状態です。npx が別の同名パッケージ(非公式の古い tsc)をダウンロードしようとしてしまうため、そのままでは次の手順も失敗します。ターミナルのカレントディレクトリが ts-practice フォルダになっているかを確認し、同じフォルダで npm install --save-dev typescript から実行し直してください。 |
バージョンが確認できたら、tsconfig.json を生成します。
npx tsc --init
以下のような実行結果が表示されます。
Created a new tsconfig.json with:
target: es2016
module: commonjs
strict: true
esModuleInterop: true
skipLibCheck: true
forceConsistentCasingInFileNames: true
tsconfig.json は、TypeScriptコンパイラ(tsc)に対する設定ファイルです。「どのバージョンのJavaScriptを出力するか」「どのファイルをコンパイル対象にするか」「型チェックをどこまで厳しくするか」といったコンパイル時の挙動を、プロジェクトごとに宣言します。TypeScriptのコマンドは、この tsconfig.json が置かれたフォルダをプロジェクトのルートとして扱います。
生成された tsconfig.json の中身は次のようになっています。ファイルには本来コメント付きで多数のオプションが並んでいますが、以下では既定で有効になっている項目だけを抜き出しています。
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
初学の段階で意識しておくべきなのは、次の3つのオプションです。
| オプション | 役割 |
|---|---|
target |
コンパイル後のJavaScriptがどのバージョンの構文で出力されるかを指定する |
module |
出力するモジュール形式を指定する(commonjs / esnext など) |
strict |
型チェックを厳しく行うかを切り替える(true を推奨) |
strict: true を有効にすると、暗黙の any や null / undefined の混入を許さない厳格モードになります。個別に有効化できるフラグがまとめてONになる便利なスイッチで、TypeScriptの恩恵を最大限受けるために基本は true のままにします。
| 💡 ポイント |
|---|
Next.jsのプロジェクトでは、npx create-next-app@latest を実行するだけで tsconfig.json が自動生成されます。ここで手動セットアップを体験しておくと、Next.jsが裏で何を用意しているかをイメージしやすくなります。 |
3.2 動作確認
tsconfig.json まで用意できた状態で、TypeScriptのコードが実際にコンパイルされ、Node.jsで実行できることを確認します。ここでは hello.ts に短いコードを書き、コンパイルと実行を通してメッセージが表示されるまでを試します。
hello.ts を作成します。Visual Studio Codeのエクスプローラーで ts-practice フォルダを右クリックし、「新しいファイル」を選択して hello.ts という名前で作成してください。
ts-practice
├── node_modules/
├── package.json
├── package-lock.json
├── tsconfig.json
└── hello.ts ← このファイルを作成
作成したファイルに以下の内容を記述して保存します。
const message: string = "TypeScriptの世界へようこそ";
console.log(message);
以下のコマンドでコンパイルを実行します。
npx tsc hello.ts
同じフォルダに hello.js が生成されるので、node コマンドで実行します。
node hello.js
以下のような実行結果が表示されます。
TypeScriptの世界へようこそ
TypeScriptの世界へようこそ が出力されれば、hello.ts から生成された hello.js が Node.js で正しく実行され、TypeScriptプロジェクトの最小構成が動作していることが動作確認できます。
コードを解説します。
const message: string = "TypeScriptの世界へようこそ";
変数 message に対し : string と型注釈を書き、その型に沿った文字列を代入しています。この : 型名 の記法がTypeScriptの最小単位の型注釈で、以降の節で扱う型定義もこの延長線上にあります。
このように、TypeScriptのコードは tsc でJavaScriptにコンパイルしてから実行するのが基本的な流れになります。
| 💡 ポイント |
|---|
ここでは動作確認のために tsc でJavaScriptに変換し、Node.jsで実行しました。実際のフロントエンド開発では、Vite・webpack・Next.jsといったバンドラが TypeScriptからJavaScriptへの変換とブラウザへの配信をまとめて自動で行います。次章で扱う Next.js では、create-next-app を実行するだけでこの仕組みが用意されるため、tsc を手動で叩く場面はほとんどなくなります。 |
4. 型注釈と型定義
TypeScriptの中心となるのが型注釈(type annotation)です。変数や関数の引数・戻り値に対して、: 型名 の形で「この値はどんな型を持つか」を宣言します。ここでは、基本のプリミティブ型と型推論から始め、配列型・any / unknown、そして interface / type によるオブジェクトの型定義まで順に扱います。
4.1 プリミティブ型
JavaScriptで扱う基本的な値には、以下の3種類のプリミティブ型があります。以降のセクションで、それぞれの型に型注釈を付ける書き方を1つずつ確認します。
number: 数値(整数と浮動小数点数の区別はない)string: 文字列boolean: 真偽値(trueまたはfalse)
number
数値を型付きの変数として扱いたいときは、number 型で型注釈を書きます。書き方は以下のとおりです。
const 変数名: number = 数値;
次のコードは、number 型の変数を宣言して出力する例です。以下のコードで hello.ts を書き換えます。
const age: number = 30;
console.log(age);
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
30
代入した 30 がそのまま出力されました。
コードを解説します。
const age: number = 30;
変数名 age の直後に : number と型注釈を書き、number 型の値である 30 を代入しています。以降、age は number として扱われ、string 型の値を代入しようとするとコンパイル時にエラーになります。
string
文字列を型付きの変数として扱いたいときは、string 型で型注釈を書きます。書き方は以下のとおりです。
const 変数名: string = "文字列";
次のコードは、string 型の変数を宣言して出力する例です。以下のコードで hello.ts を書き換えます。
const name: string = "田中太郎";
console.log(name);
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
田中太郎
代入した "田中太郎" がそのまま出力されました。
コードを解説します。
const name: string = "田中太郎";
name に : string と型注釈を書き、文字列 "田中太郎" を代入しています。ダブルクォート・シングルクォート・バッククォートのいずれで囲んでも string として扱われます。
boolean
真偽値を型付きの変数として扱いたいときは、boolean 型で型注釈を書きます。書き方は以下のとおりです。
const 変数名: boolean = true;
次のコードは、boolean 型の変数を宣言して出力する例です。以下のコードで hello.ts を書き換えます。
const isActive: boolean = true;
console.log(isActive);
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
true
代入した true がそのまま出力されました。
コードを解説します。
const isActive: boolean = true;
isActive に : boolean と型注釈を書き、true を代入しています。boolean は true と false の2値のみを取ります。
4.2 型推論
変数に型を事前に定義せずに、代入した値から型を自動で判定してくれる仕組みを、型推論(type inference)と呼びます。TypeScriptでは、宣言と同時に値を代入する場合には代入値から型が推論されるため、: 型名 の記述を省略できます。
次のコードは、型注釈を書かずに宣言した変数に対して、後から違う型の値を代入しようとする例です。以下のコードで hello.ts を書き換えます。
let age = 30;
age = "三十歳";
age は 30 を代入した時点で number として推論されるため、以下のコマンドでコンパイルすると、後から文字列を代入しようとしている行でエラーが表示されます。
npx tsc hello.ts
Type 'string' is not assignable to type 'number'.
Type 'string' is not assignable to type 'number'. のエラーが表示されれば、型注釈を省略した変数でも、推論された型と違う型の値を代入しようとした時点で TypeScript がコンパイル時にエラーとして検出できていることが動作確認できます。
コードを解説します。
let age = 30;
型注釈を書いていませんが、代入値が 30 なので age は number 型として推論されます。以降 age は number として扱われます。
age = "三十歳";
number 型として扱われる変数に文字列を代入しようとしたため、コンパイル時にエラーになります。型推論を使う場合でも、後からの再代入は最初に推論された型で縛られる点に注意します。
4.3 配列型
配列に型を付けたいときは、型名[] の形で「要素の型」を指定します。書き方は以下のとおりです。
const 変数名: 要素の型[] = [要素, 要素, /* ... */];
次のコードは、number の配列と string の配列を宣言して出力する例です。以下のコードで hello.ts を書き換えます。
const scores: number[] = [80, 90, 75];
const fruits: string[] = ["apple", "banana", "orange"];
console.log(scores);
console.log(fruits);
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
[ 80, 90, 75 ]
[ 'apple', 'banana', 'orange' ]
scores と fruits が、それぞれ number と string の配列として出力されました。
コードを解説します。
const scores: number[] = [80, 90, 75];
要素の型 number に [] を付け、number[] として配列全体の型を宣言しています。配列に別の型の要素を追加しようとするとコンパイルエラーになります。
const fruits: string[] = ["apple", "banana", "orange"];
要素の型 string に [] を付けた string[] として宣言しています。書き方の形は number[] とまったく同じで、要素の型だけを差し替えます。
Array<型名> というジェネリクスの形でも同じ意味になります。単純な要素型のときは 型名[] が一般的です。
const scores: Array<number> = [80, 90, 75];
配列に違う型の要素を混ぜようとすると、コンパイル時にエラーになります。以下のコードで hello.ts を書き換えます。
const scores: number[] = [80, 90, "満点"];
以下のコマンドでコンパイルすると、エラーが表示されます。
npx tsc hello.ts
Type 'string' is not assignable to type 'number'.
Type 'string' is not assignable to type 'number'. のエラーが表示されれば、配列に宣言した要素型と違う型の値を混ぜたときにコンパイルエラーが出ることが動作確認できます。
any と unknown
TypeScriptには、型を厳密に決められない値を扱うための any と unknown という2つの型があります。用途が似ているようでいて安全性がまったく違うので、それぞれの動きを分けて確認します。
any
型チェックを一時的に無効化したい値には、any を使います。JavaScriptで書かれた既存コードを段階的にTypeScript化する場面など、型を書ききれない箇所のために用意されている型で、あらゆる値を代入でき、プロパティアクセスや演算のチェックも行われません。書き方は以下のとおりです。
let 変数名: any = 値;
次のコードは、any で宣言した変数に別々の型の値を代入し、危険なプロパティアクセスをしても静的解析ではエラーにならない例です。以下のコードで hello.ts を書き換えます。
let value: any = 10;
value = "文字列でもOK";
value = { key: "オブジェクトでもOK" };
value.foo.bar.baz;
以下のコマンドでコンパイルします。コンパイル時にはエラーになりません。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行時エラーが表示されます。
TypeError: Cannot read properties of undefined (reading 'bar')
value.foo.bar.baz のような明らかに危険なアクセスもコンパイル時にはエラーにならず、実際に実行したときに初めて TypeError として現れます。これでは型を導入した意味がありません。
コードを解説します。
let value: any = 10;
value = "文字列でもOK";
value = { key: "オブジェクトでもOK" };
any はあらゆる型を許容するため、number → string → オブジェクトと再代入しても静的解析はエラーになりません。
value.foo.bar.baz;
value は現在オブジェクトですが、foo プロパティを定義していないので実行時に TypeError になります。にもかかわらず、any に対するプロパティアクセスは一切チェックされないため、コンパイル時には問題が検出されません。
unknown
型を厳密に決められないが型安全は維持したいときは、unknown を使います。どんな値でも代入できますが、そのままではプロパティアクセスや演算ができず、typeof や instanceof による型ガードでチェックしてからでないと使えません。書き方は以下のとおりです。
function 関数名(引数名: unknown) {
if (typeof 引数名 === "型名") {
// このブロック内では、引数名は指定した型として扱える
}
}
次のコードは、unknown として受け取った値の型を絞り込んでから、文字列専用のメソッドを呼び出す例です。以下のコードで hello.ts を書き換えます。
function printLength(value: unknown) {
if (typeof value === "string") {
console.log(value.length);
} else {
console.log("文字列ではありません");
}
}
printLength("hello");
printLength(123);
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
5
文字列ではありません
"hello" は string として文字数の 5 が返り、123 は文字列ではないメッセージが返りました。
コードを解説します。
function printLength(value: unknown) {
引数 value を unknown として受け取っています。この時点では value.length のようなプロパティアクセスはコンパイル時にエラーとして扱われ、直接使えません。
if (typeof value === "string") {
console.log(value.length);
}
typeof value === "string" が true の分岐内では、TypeScriptは value を string として扱ってくれます。そのため、string にしかない .length プロパティを安全に使えます。
unknown を経由することで、「値の中身が判明する前に間違った操作をしてしまう」ミスをコンパイル時に防げます。この分岐の仕組み自体は「ユニオン型と型ガード」節でより詳しく扱います。
📝 any を避ける理由 |
|---|
any を許容してしまうと、その値が別の関数に渡された先でもチェックが行われず、型安全が波及的に失われます。どうしても型を書けない値は unknown で受け取り、使う直前に型ガードで絞り込むのが安全です。 |
interface による定義
オブジェクトの形(どんなプロパティを持ち、それぞれがどんな型か)に名前を付けて再利用したいときは、interface を使います。書き方は以下のとおりです。
interface インターフェース名 {
プロパティ名: 型;
// ...
}
次のコードは、User の interface を定義し、その型に沿ったオブジェクトを作る例です。以下のコードで hello.ts を書き換えます。
interface User {
id: number;
name: string;
email: string;
}
const user: User = {
id: 1,
name: "田中太郎",
email: "tanaka@example.com",
};
console.log(user);
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
{ id: 1, name: '田中太郎', email: 'tanaka@example.com' }
interface に沿った形のオブジェクトが user に代入され、そのまま出力されました。
コードを解説します。
interface User {
id: number;
name: string;
email: string;
}
User という名前で、id(number)・name(string)・email(string)の3つのプロパティを持つオブジェクトの型を定義しています。プロパティ名の直後に : を書き、続けてその値の型を並べます。
const user: User = {
id: 1,
name: "田中太郎",
email: "tanaka@example.com",
};
変数 user の型として User を指定しています。代入するオブジェクトは、User で定義したプロパティをすべて備えている必要があります。
プロパティが足りなかったり、余計なプロパティが含まれていたりすると、コンパイル時にエラーとして検出されます。以下のコードで hello.ts を書き換えます。
interface User {
id: number;
name: string;
email: string;
}
const user: User = {
id: 1,
name: "田中太郎",
};
以下のコマンドでコンパイルすると、エラーが表示されます。
npx tsc hello.ts
Property 'email' is missing in type '{ id: number; name: string; }' but required in type 'User'.
Property 'email' is missing 〜 のエラーが表示されれば、interface で宣言した必須プロパティが欠けているオブジェクトを、TypeScriptがコンパイル時に検出できていることが動作確認できます。
type による定義
オブジェクトの形に別名を付けたいときは、type でも表現できます。書き方は以下のとおりです。
type 型名 = {
プロパティ名: 型;
// ...
};
次のコードは、User を type で定義し、interface と同じように使う例です。以下のコードで hello.ts を書き換えます。
type User = {
id: number;
name: string;
email: string;
};
const user: User = {
id: 1,
name: "田中太郎",
email: "tanaka@example.com",
};
console.log(user);
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
{ id: 1, name: '田中太郎', email: 'tanaka@example.com' }
interface を type に差し替えても、オブジェクトの形の定義としてはまったく同じ結果になります。
コードを解説します。
type User = {
id: number;
name: string;
email: string;
};
type は「任意の型に別名を付ける構文」で、右辺にオブジェクトの形を直接書けます。オブジェクトの形の定義としては interface と互換ですが、type のほうはユニオン型(string | number など)やプリミティブ型の別名にも使える点が異なります。
interface と type の主な違いは以下のとおりです。
| 観点 | interface |
type |
|---|---|---|
| オブジェクトの形の定義 | 自然に書ける | 自然に書ける |
ユニオン型(string | number など) |
使えない | 使える |
| 同名の宣言をマージ | できる(宣言のマージ) | できない |
多くのプロジェクトでは、「オブジェクトの形を表すときは interface、ユニオン型やプリミティブの別名は type」と使い分けるのが慣例です。type に統一しているプロジェクトも多く、どちらでも大きな支障はありません。
4.4 オプショナルプロパティ
一部のプロパティを省略可能にしたいときは、プロパティ名の後ろに ? を付けます。書き方は以下のとおりです。
interface インターフェース名 {
必須プロパティ名: 型;
省略可プロパティ名?: 型;
}
次のコードは、email を省略可能にした User を定義し、email あり・なしの両方でオブジェクトを作る例です。以下のコードで hello.ts を書き換えます。
interface User {
id: number;
name: string;
email?: string;
}
const userA: User = { id: 1, name: "田中太郎" };
const userB: User = { id: 2, name: "鈴木花子", email: "hanako@example.com" };
console.log(userA);
console.log(userB);
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
{ id: 1, name: '田中太郎' }
{ id: 2, name: '鈴木花子', email: 'hanako@example.com' }
email を省略した userA もエラーにならず、email を含めた userB と並行して代入できました。
コードを解説します。
interface User {
id: number;
name: string;
email?: string;
}
email?: string と書くことで「あれば string、無くてもよい」プロパティになります。省略された場合、そのプロパティは undefined として扱われるため、参照時は存在チェックが必要になります。
readonly
宣言後に値を書き換えられないプロパティを表現したいときは、readonly を付けます。書き方は以下のとおりです。
interface インターフェース名 {
readonly プロパティ名: 型;
}
次のコードは、id を readonly として定義したあとに書き換えようとする例です。以下のコードで hello.ts を書き換えます。
interface User {
readonly id: number;
name: string;
}
const user: User = { id: 1, name: "田中太郎" };
user.id = 2;
以下のコマンドでコンパイルすると、エラーが表示されます。
npx tsc hello.ts
Cannot assign to 'id' because it is a read-only property.
Cannot assign to 'id' because it is a read-only property. のエラーが表示されれば、readonly を付けたプロパティに代入しようとした時点で TypeScript がコンパイル時にエラーとして検出できていることが動作確認できます。
コードを解説します。
interface User {
readonly id: number;
name: string;
}
readonly を付けたプロパティは、オブジェクトを作るときに一度だけ値を代入できます。以降の再代入はコンパイル時に禁止されるため、識別子(id)のように「後から書き換わってほしくない値」に付けます。
4.5 ネストしたオブジェクト
オブジェクトのプロパティがさらにオブジェクトになる場合も、同じように interface で型を定義していきます。次のコードは、User の中に住所を表す Address をネストさせた例です。以下のコードで hello.ts を書き換えます。
interface Address {
city: string;
zipCode: string;
}
interface User {
id: number;
name: string;
address: Address;
}
const user: User = {
id: 1,
name: "田中太郎",
address: {
city: "東京都",
zipCode: "100-0001",
},
};
console.log(user);
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
{ id: 1, name: '田中太郎', address: { city: '東京都', zipCode: '100-0001' } }
user.address に Address の形のオブジェクトがネストされ、そのまま出力されました。
コードを解説します。
interface Address {
city: string;
zipCode: string;
}
Address を独立した interface として定義しています。名前を付けておくことで、他の interface のプロパティとしても再利用できます。
interface User {
id: number;
name: string;
address: Address;
}
User の address プロパティの型として Address を指定しています。ネストしたオブジェクトも、Address のような名前付きの型に切り出すことで意図が読み取りやすくなります。
5. ユニオン型と型ガード
APIのレスポンスや状態管理では、「文字列または数値」「成功または失敗」といった複数のパターンを持つ値を扱う場面がよくあります。TypeScriptではこれをユニオン型(| で区切る型)で表現します。ここでは、ユニオン型・リテラル型・型ガード・判別可能ユニオンをまとめて扱います。
5.1 ユニオン型
「文字列または数値のどちらか」のように、複数の型のいずれかを受け取れる値を表したいときは、ユニオン型を使います。書き方は以下のとおりです。
型1 | 型2 | /* ... */
次のコードは、id として string と number のどちらでも受け取れる関数の例です。以下のコードで hello.ts を書き換えます。
function formatId(id: string | number): string {
return `ID: ${id}`;
}
console.log(formatId(123));
console.log(formatId("abc"));
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
ID: 123
ID: abc
123(number)と "abc"(string)のどちらも受け付けられ、それぞれ ID: 123 と ID: abc が出力されました。
コードを解説します。
function formatId(id: string | number): string {
id の型として string | number を指定しています。呼び出し側は文字列か数値のどちらかを渡すことができ、それ以外の型を渡そうとするとコンパイルエラーになります。
5.2 リテラル型
決まった選択肢の中から1つだけを許可する型を作りたいときは、リテラル型を使います。値そのものを型として扱う仕組みで、ユニオン型と組み合わせて表現するのが一般的です。書き方は以下のとおりです。
type 型名 = "値1" | "値2" | "値3";
次のコードは、状態を表す Status を「success」「error」「loading」の3つに絞ったリテラル型で定義し、それを引数に受け取る関数の例です。候補にない値を渡すとどうなるかも確認します。以下のコードで hello.ts を書き換えます。
type Status = "success" | "error" | "loading";
function showMessage(status: Status) {
console.log(`現在の状態: ${status}`);
}
showMessage("success");
showMessage("pending");
"pending" は Status の候補にないため、以下のコマンドでコンパイルすると、エラーが表示されます。
npx tsc hello.ts
Argument of type '"pending"' is not assignable to parameter of type 'Status'.
Argument of type '"pending"' is not assignable to parameter of type 'Status'. のエラーが表示されれば、リテラル型で列挙した候補以外の文字列をコンパイル時にエラーとして検出できていることが動作確認できます。
コードを解説します。
type Status = "success" | "error" | "loading";
Status は「success か error か loading のいずれかの文字列」しか許容しない型です。文字列値そのものが型として扱われ、それをユニオン型で並べています。
showMessage("pending");
"pending" は Status の候補に含まれない文字列のため、コンパイル時にエラーとして検出されます。if (status === "sucess") のような書き間違いも即座に指摘されるため、有限の選択肢を扱う場面で有効です。
5.3 型ガード
ユニオン型を受け取った関数の中で、値がどちらの型なのかによって処理を分岐したいときは、型ガードを使います。typeof や instanceof を条件式に書くと、その分岐内で型が絞り込まれます。書き方は以下のとおりです。
if (typeof 値 === "型名") {
// このブロック内では、値は指定した型として扱える
}
次のコードは、string | number を受け取り、型ごとに異なる整形処理を行う関数の例です。以下のコードで hello.ts を書き換えます。
function formatId(id: string | number): string {
if (typeof id === "string") {
return `ID: ${id.toUpperCase()}`;
}
return `ID: ${id.toFixed(0)}`;
}
console.log(formatId("abc"));
console.log(formatId(123.7));
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
ID: ABC
ID: 124
"abc" は string として大文字化され、123.7 は number として小数点以下を切り捨てた形で出力されました。
コードを解説します。
if (typeof id === "string") {
return `ID: ${id.toUpperCase()}`;
}
typeof id === "string" が true のとき、TypeScriptは id を string 型として扱います。そのため、string にしかない toUpperCase() メソッドを呼び出せます。
return `ID: ${id.toFixed(0)}`;
if を抜けたあとの id は number 型として扱われます。toFixed() は number のメソッドで、指定した桁数に丸めた文字列を返します。
5.4 判別可能ユニオン
複数のオブジェクトをまとめてユニオン型にしたいときは、判別可能ユニオン(discriminated union)を使います。各オブジェクトに共通の判別子プロパティを持たせ、その値で分岐することで、分岐先ごとに扱えるプロパティを型で区別できます。書き方は以下のとおりです。
type 型名A = { 判別子: "値A"; プロパティ名: 型 };
type 型名B = { 判別子: "値B"; プロパティ名: 型 };
type ユニオン型名 = 型名A | 型名B;
次のコードは、APIレスポンスを「成功」と「失敗」の2種類のオブジェクトで表し、status プロパティで分岐する例です。以下のコードで hello.ts を書き換えます。
type SuccessResponse = { status: "success"; data: string };
type ErrorResponse = { status: "error"; message: string };
type ApiResponse = SuccessResponse | ErrorResponse;
function handleResponse(response: ApiResponse) {
if (response.status === "success") {
console.log(`データ: ${response.data}`);
} else {
console.log(`エラー: ${response.message}`);
}
}
handleResponse({ status: "success", data: "取得したデータ" });
handleResponse({ status: "error", message: "サーバエラー" });
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
データ: 取得したデータ
エラー: サーバエラー
成功時は data、失敗時は message を参照して、それぞれのメッセージが出力されました。
コードを解説します。
type SuccessResponse = { status: "success"; data: string };
type ErrorResponse = { status: "error"; message: string };
type ApiResponse = SuccessResponse | ErrorResponse;
SuccessResponse と ErrorResponse の両方に status という共通プロパティを持たせ、その値をリテラル型("success" / "error")で区別しています。ApiResponse はこの2つのユニオン型です。
if (response.status === "success") {
console.log(`データ: ${response.data}`);
} else {
console.log(`エラー: ${response.message}`);
}
response.status の値で分岐したとき、"success" の分岐内では SuccessResponse として扱われて data のみアクセスでき、else の分岐では ErrorResponse として扱われて message のみアクセスできます。「成功確認なしに data を参照する」ミスを型レベルで防げるため、APIレスポンスの成功/失敗を型で表現するときによく使われます。
6. 関数の型
TypeScriptでは値の型を明示する必要があるため、関数の引数や戻り値にも型を付ける必要があります。ここでは、通常の関数・関数型・コールバック・void・非同期関数の順で、それぞれの型の書き方を扱います。
6.1 引数と戻り値
関数の引数と戻り値に型を付けたいときは、引数の後ろに : 型名 を、戻り値は引数リストの後ろに : 型名 を書きます。書き方は以下のとおりです。
function 関数名(引数名: 型, 引数名: 型 /* ... */): 戻り値の型 {
// 処理
}
次のコードは、2つの数値を受け取り、その合計を返す関数の例です。以下のコードで hello.ts を書き換えます。
function add(a: number, b: number): number {
return a + b;
}
const result: number = add(3, 5);
console.log(result);
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
8
add(3, 5) の戻り値である 8 が result に代入され、出力されました。
コードを解説します。
function add(a: number, b: number): number {
引数 a と b それぞれに : number を付けて型を指定しています。引数リストの後ろの : number は、この関数が返す値の型です。宣言と異なる型を返そうとすると、コンパイルエラーで検出できます。
戻り値の型は省略しても推論されますが、公開する関数では明示することでシグネチャが読みやすくなります。関数内部で誤って別の型を返してしまった場合も、宣言と食い違えばコンパイルエラーとして検出できます。
6.2 関数型
引数として関数を受け取ったり、変数に関数を代入したりしたいときは、関数型で表現します。書き方は以下のとおりです。
(引数名: 型, /* ... */) => 戻り値の型
次のコードは、乗算関数を変数に代入し、その変数の型を関数型で先に決めておく例です。以下のコードで hello.ts を書き換えます。
const multiply: (x: number, y: number) => number = (x, y) => x * y;
console.log(multiply(3, 4));
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
12
multiply(3, 4) の結果である 12 が出力されました。
コードを解説します。
const multiply: (x: number, y: number) => number = (x, y) => x * y;
multiply の型として (x: number, y: number) => number を宣言しています。左辺で関数型を先に決めているため、右辺のアロー関数側では引数の型を省略しても、それぞれ number として扱われます。
6.3 コールバック関数の型
配列の map や filter に渡すコールバック関数にも、同じ書式で型を付けられます。次のコードは、number[] の各要素を2倍にするコールバックに、引数と戻り値の型を明示した例です。以下のコードで hello.ts を書き換えます。
const numbers: number[] = [1, 2, 3, 4, 5];
const doubled: number[] = numbers.map((n: number): number => n * 2);
console.log(doubled);
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
[ 2, 4, 6, 8, 10 ]
配列の各要素が2倍された [ 2, 4, 6, 8, 10 ] が出力され、doubled が number[] として得られています。
コードを解説します。
const doubled: number[] = numbers.map((n: number): number => n * 2);
(n: number): number => n * 2 の部分で、コールバックの引数 n を number として受け取り、戻り値も number であることを明示しています。map の戻り値も要素の型が number の配列になるため、doubled を number[] として受け取れます。
実際には map の型定義から n の型は number と推論されるため、以下のように引数と戻り値の型は省略しても補完・チェックは効きます。
const doubled = numbers.map((n) => n * 2);
void 型
戻り値を返さない関数の型として使いたいときは、void を指定します。書き方は以下のとおりです。
function 関数名(引数名: 型): void {
// 戻り値を返さない処理
}
次のコードは、メッセージをコンソールに出力するだけで戻り値を返さない関数の例です。以下のコードで hello.ts を書き換えます。
function logMessage(message: string): void {
console.log(message);
}
logMessage("処理を開始しました");
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
処理を開始しました
logMessage("処理を開始しました") により、引数の文字列がそのまま出力されました。
コードを解説します。
function logMessage(message: string): void {
console.log(message);
}
戻り値の型として void を指定しています。void は「戻り値を使わない」ことを示す型で、return 文を省略するか、値を伴わない return を書きます。呼び出し側で logMessage(...) の戻り値を変数に代入して使うようなコードは、型として想定されなくなります。
6.4 非同期関数(Promise)
前章で学んだ非同期関数を型付きで表現したいときは、戻り値の型を Promise<T> で書きます。T にはPromiseが解決したときに返る値の型が入ります。書き方は以下のとおりです。
async function 関数名(引数名: 型): Promise<解決値の型> {
// 非同期処理
return 値;
}
次のコードは、指定したIDのユーザ情報をJSONPlaceholderから取得し、interface で data の型を絞り込んだ上で name を返す関数の例です。以下のコードで hello.ts を書き換えます。
interface User {
id: number;
name: string;
email: string;
}
async function fetchUserName(id: number): Promise<string> {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
const data: User = await response.json();
return data.name;
}
fetchUserName(1).then((name) => console.log(name));
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
Leanne Graham
JSONPlaceholderが返した id: 1 のユーザ情報から、name プロパティが取り出されて出力されました。
コードを解説します。
async function fetchUserName(id: number): Promise<string> {
async を付けているため、この関数の戻り値は自動的にPromiseで包まれます。Promise<string> と明示することで、「解決したときに string を返す非同期関数である」ことが型で示されます。
const data: User = await response.json();
response.json() の戻り値は型定義上 Promise<any> になっているため、放っておくと data は any として扱われてしまいます。: User を書くことで「レスポンスは User の形である」と明示し、以降 data.name のようなアクセスに型チェックが効くようにしています。
fetchUserName の呼び出し側では then((name) => ...) の name が string として推論されるので、大文字化などの string 専用のメソッドを続けて呼び出しても補完が効きます。
| 📝 TypeScriptの型は実行時には検証されない |
|---|
await response.json() の結果に : User と型を付けても、TypeScriptは実行時のデータ検証まではしません。APIが期待通りの形を返してくれるという前提での型付けなので、外部APIを使う場合はzodなどのバリデーションライブラリと組み合わせるのが一般的です。ここではまず、型注釈の書き方に慣れることを優先します。 |
7. ジェネリクス
ジェネリクス(generics)は、型を後から差し替えられる仕組みです。配列やPromiseのように「中身の型は状況によって変わるが、構造は共通」というパターンを表現するために使います。
Array<T> と Promise<T> の見方
TypeScriptのコードで頻出する Array<T> は「T の配列」、Promise<T> は「T を解決するPromise」を意味します。角括弧に似た <> の中に入れた型が、その汎用的な型の「中身」を指定します。
const scores: Array<number> = [80, 90, 75];
const request: Promise<string> = fetch("https://example.com").then((r) => r.text());
Array<T> は T[] と等価なので、単純な配列型は T[] の書き方でよく、Promise<T> は書き換えの効かない専用の形として頻出します。
7.1 ジェネリクス関数の自作
任意の値を受け取り、そのまま返す関数を考えてみます。引数の型と戻り値の型が同じであることを表現したい場合、any を使うと型情報が失われてしまいます。
function identity(value: any): any {
return value;
}
const result = identity("hello");
この場合 result は any になってしまい、.toUpperCase() などのメソッドの候補も出てきません。呼び出し側で決まる型をそのまま戻り値に反映したいときは、ジェネリクスを使います。書き方は以下のとおりです。
function 関数名<型パラメータ>(引数名: 型パラメータ): 戻り値の型 {
// 処理
}
次のコードは、渡された値をそのまま返す関数を、ジェネリクスで型を保ったまま実装した例です。以下のコードで hello.ts を書き換えます。
function identity<T>(value: T): T {
return value;
}
const text = identity<string>("hello");
const num = identity<number>(123);
console.log(text.toUpperCase());
console.log(num.toFixed(0));
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
HELLO
123
identity<string>("hello") は text を string として扱うため toUpperCase() が呼べ、identity<number>(123) は num を number として扱うため toFixed(0) が呼べます。
コードを解説します。
function identity<T>(value: T): T {
return value;
}
<T> で「呼び出し時に決まる型」の目印を宣言し、value の型と戻り値の型の両方に T を使っています。これにより、渡した値の型がそのまま戻り値の型として引き継がれます。
<T> の T は型のパラメータで、名前は自由に付けられます。T(Type)・U・K(Key)・V(Value)などが慣例的に使われます。呼び出し側で identity<string>("hello") のように型を明示するか、TypeScriptの推論に任せて identity("hello") と書くこともできます。
7.2 よく使うユーティリティ型
TypeScriptにはあらかじめ定義されたユーティリティ型が多数用意されています。すべて覚える必要はありませんが、以下の4つは実際の開発で頻繁に登場するので、それぞれ動きを確認します。詳しい種類は Utility Types(TypeScript公式ドキュメント) に記載があります。
Record<K, V>
キーの型と値の型を指定して、そのペアからなるオブジェクトの型を作りたいときは、Record<K, V> を使います。書き方は以下のとおりです。
Record<キーの型, 値の型>
次のコードは、Status の各値に対応するラベル文字列を持つオブジェクトを、Record で型付けする例です。以下のコードで hello.ts を書き換えます。
const statusLabels: Record<"success" | "error" | "loading", string> = {
success: "成功",
error: "エラー",
loading: "読み込み中",
};
console.log(statusLabels.success);
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
成功
statusLabels.success に対応する "成功" が出力されました。
コードを解説します。
const statusLabels: Record<"success" | "error" | "loading", string> = {
success: "成功",
error: "エラー",
loading: "読み込み中",
};
キーの型を "success" | "error" | "loading" のリテラル型のユニオン、値の型を string にしています。この型を持つオブジェクトは、指定した3つのキーをすべて備え、それ以外のキーは許容されないため、キーの取りこぼしや誤字をコンパイル時に防げます。
Partial<T>
既存の型のすべてのプロパティを省略可能にした型を作りたいときは、Partial<T> を使います。書き方は以下のとおりです。
Partial<型名>
次のコードは、User のプロパティを部分的に更新する updateUser 関数を、Partial<User> で受け取る例です。以下のコードで hello.ts を書き換えます。
interface User {
id: number;
name: string;
email: string;
}
function updateUser(id: number, patch: Partial<User>) {
console.log(`user ${id} を更新:`, patch);
}
updateUser(1, { email: "new@example.com" });
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
user 1 を更新: { email: 'new@example.com' }
email プロパティだけを渡した patch がそのまま出力されました。
コードを解説します。
function updateUser(id: number, patch: Partial<User>) {
patch の型として Partial<User> を指定しています。この型は User のすべてのプロパティを省略可能(? 付き)にした形で、「一部だけを更新する」用途にちょうど合います。
Pick<T, K>
既存の型から特定のプロパティだけを取り出した型を作りたいときは、Pick<T, K> を使います。書き方は以下のとおりです。
Pick<型名, "プロパティ名1" | "プロパティ名2">
次のコードは、User から id と name だけを取り出した UserSummary を定義する例です。以下のコードで hello.ts を書き換えます。
interface User {
id: number;
name: string;
email: string;
}
type UserSummary = Pick<User, "id" | "name">;
const summary: UserSummary = { id: 1, name: "田中太郎" };
console.log(summary);
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
{ id: 1, name: '田中太郎' }
UserSummary の形に沿った id と name だけのオブジェクトが出力されました。
コードを解説します。
type UserSummary = Pick<User, "id" | "name">;
Pick<User, "id" | "name"> は、User から id と name のプロパティだけを取り出した型を生成します。一覧表示など、必要最小限の情報だけを持つ型が欲しい場面で使います。
Omit<T, K>
既存の型から特定のプロパティを除いた型を作りたいときは、Omit<T, K> を使います。書き方は以下のとおりです。
Omit<型名, "プロパティ名1" | "プロパティ名2">
次のコードは、User から id を除いた NewUserInput を定義し、新規作成時の入力型として使う例です。IDは関数内で採番するため、入力側では受け取らない前提です。以下のコードで hello.ts を書き換えます。
interface User {
id: number;
name: string;
email: string;
}
type NewUserInput = Omit<User, "id">;
function createUser(input: NewUserInput): User {
const newId = Date.now();
return { id: newId, ...input };
}
const created = createUser({ name: "山田花子", email: "yamada@example.com" });
console.log(created);
以下のコマンドでコンパイルします。
npx tsc hello.ts
続いて、生成された hello.js を実行します。
node hello.js
以下のような実行結果が表示されます。
{ id: 1721196000000, name: '山田花子', email: 'yamada@example.com' }
呼び出し側では id を渡さず name と email だけを指定し、関数内で採番された id を含む User が返っています。
コードを解説します。
type NewUserInput = Omit<User, "id">;
Omit<User, "id"> は、User から id を除いたプロパティで構成される型を生成します。createUser の引数 input の型として使うことで、呼び出し側が誤って id を渡そうとするとコンパイル時にエラーになり、「id は関数内で必ず採番される」ことを型で担保できます。
8. まとめ
この章では、TypeScriptへの移行を学びつつ、実際にコードで型付けを確かめました。
- TypeScriptは、JavaScriptに型を加えた言語で、実行前に型エラーを検出できる
tsc --initで生成されるtsconfig.jsonのstrictを有効にすることで、厳格な型チェックが働く- 型注釈は、変数・関数の引数・戻り値に
: 型名を書いて指定でき、numberstringbooleanなどのプリミティブ型と、型名[]の配列型が基本になる anyは型チェックを無効化するため避け、型が定まらない値はunknownで受け取ってから型ガードで絞り込む- オブジェクトの形は
interfaceまたはtypeで定義でき、?でオプショナル、readonlyで読み取り専用のプロパティを表現できる - ユニオン型とリテラル型により、複数パターンを表現でき、
typeofなどの型ガードや判別可能ユニオンで安全に分岐できる - 関数の型は引数・戻り値に型を付け、非同期関数は
Promise<T>で表現する - ジェネリクスは「型を後から差し替える」仕組みで、
RecordPartialPickOmitなどのユーティリティ型を活用して型を派生できる
次の章では、Reactベースのフレームワークである Next.js の基本を学びつつ、実際にプロジェクトを作成して動作を体験します。