📘 TypeScript
Study Roomの資料は、keysubがAIを活用して作成・編集しています。
1. はじめに 🚀
展開 / 折りたたみ
1.1 TypeScriptとは? 🤔
TypeScriptはMicrosoftが開発したプログラミング言語で、簡単に言えば「JavaScriptに型を加えた言語」です。
1.2 JavaScriptのスーパーセットとしての位置づけ 🔝
1.2.1. 「スーパーセット」とは
数学でいう包含関係を考えると分かりやすいでしょう。JavaScriptが「小さな円」なら、TypeScriptはそれを完全に含む「より大きな円」です。
* 互換性: JavaScriptのコードはすべて有効なTypeScriptコードです。既存の.js拡張子を.tsに変えるだけで問題なく動作します。
* 追加要素: JavaScriptの構文に「静的型システム」という強力な機能を加えたものです。
1.2.2. なぜJavaScriptに型を加えたのか
JavaScriptは柔軟ですが、規模が大きくなるとエラーの発生箇所や内容を予測しにくくなります。TypeScriptはこの欠点を補います。 * エラーの事前防止: コードの実行前(コンパイル時)にタイプミスや誤ったデータ型の使用を検出します。 * ガイドの役割: 変数や関数がどのデータを受け渡すか明示するため、チームメンバー(または未来の自分)がコードを理解しやすくなります。 * ツールのサポート: オートコンプリートが大幅に向上し、開発速度と正確性が高まります。
1.2.3. 仕組み:ブラウザーはTypeScriptを理解できない? ⚙️
ブラウザーはTypeScriptを直接読み込めないため、コンパイル(またはトランスパイル)が必要です。
1. .tsファイルにコードを書く
2. コンパイラーが型をチェックしてエラーを確認する
3. 問題がなければ型を取り除き、通常の.jsファイルに変換する
4. ブラウザーが最終的なJavaScriptファイルを実行する
1.3 動的型付け言語(JS)と静的型付け言語(TS)の違い ⚖️
動的型付け(JS)と静的型付け(TS)の大きな違いは、型がいつ決まるか、エラーがいつ見つかるかです。
| 項目 | JavaScript(動的型付け) | TypeScript(静的型付け) |
|---|---|---|
| 型が決まる時点 | 実行時に変数へ値を代入したとき自動的に決まる | コード作成時に開発者が型を明示する |
| エラーの検出時点 | プログラムを実行して初めてエラーが分かる | プログラムの実行前、コンパイル時にエラーを検出する |
| 柔軟性/安全性 | 柔軟だが、予期しない型エラーが起きやすい | 厳格だが、コードの安定性と予測可能性が高い |
1.3.1. JavaScript(動的型付け)🏃
同じ変数に数値を入れた後で、文字列を入れることもできます。
let value = 10; // 最初は数値
value = "こんにちは"; // 突然文字列に変えてもOK(実行まで問題は分からない)
1.3.2. TypeScript(静的型付け)🛡️
変数を作成するとき、どの型の値を格納するかをあらかじめ決めます。
let value: number = 10;
value = "こんにちは"; // エラー!「数値型に文字列は代入できません。」
2. 基本型 💎
展開 / 折りたたみ
2.1 プリミティブ型 📋
2.1.1. string:テキストデータを格納
let userName: string = "Alice";
let greeting: string = `Hello, ${userName}`; // テンプレートリテラルも使用可能
2.1.2. number:整数・小数を問わずすべての数値を格納
let age: number = 25;
let pi: number = 3.14;
2.1.3. boolean:真(true)または偽(false)
let isLoggedIn: boolean = true;
let hasToken: boolean = false;
2.1.4. null & undefined
通常は「値がない」ことを明示するときに使います。
let empty: null = null;
let notFound: undefined = undefined;
2.2 配列とタプル 🏗️
配列とタプルはデータ構造化の要です。配列は同じ型の一覧に、タプルは順序と要素数が決まった一覧に使います。
2.2.1 配列型の宣言方法:type[]とArray<type>
// 方法1:type[](最も一般的)
const productList: string[] = ["iPhone", "Galaxy", "MacBook"];
// 方法2:Array<type>(ジェネリック形式)
const priceList: Array<number> = [1000, 2000, 3000];
2.2.2 オブジェクトの配列 📦
サーバーから取得する会員一覧や商品リストのような形式です。
2.2.2.1. オブジェクト配列の扱い方:[1. オブジェクトの形を定義] → [2. 配列に適用]
- interfaceと
[]の組み合わせ(最も一般的)
// 1. オブジェクトの設計図(interface)を作成
interface User {
id: number;
name: string;
}
// 2. その型を配列に適用
const users: User[] = [
{ id: 1, name: "Kim" },
{ id: 2, name: "Lee" }
];
users.push({ id: 3, name: "Park" }); // 自由に追加可能
- typeエイリアスと
Array<>の組み合わせ
type User = {
id: number;
name: string;
};
const users: Array<User> = [
{ id: 1, name: "Kim" }
];
- 柔軟な型定義(オプショナルプロパティ)。ここでは
?を使います。
interface User {
id: number;
name: string;
email?: string; // メールは値があればstring、なければundefined
}
const userList: User[] = [
{ id: 1, name: "Kim", email: "kim@test.com" },
{ id: 2, name: "Lee" } // emailがなくてもエラーにならない
];
- 読み取り専用配列(
Readonly)
const colors: readonly string[] = ["Red", "Blue"];
colors.push("Green"); // エラー!(追加不可)
2.2.3 タプル 🍱
-
タプルとは、順序と要素数が決まったデータの組を意味する数学・コンピューターサイエンス用語です。 日常生活にたとえると、セットメニューのようなものです。
ハンバーガーセットには、[ハンバーガー、コーラ、フライドポテト]という順序と内容が決まっています。 -
TypeScriptのタプルは配列のサブタイプで、要素数が固定され、各要素の型があらかじめ決まった配列です。
2.2.3.1 基本的な宣言と代入 📍
各インデックスに入る型を順番に指定します。
let person: [string, number];
person = ["Alice", 30]; // ✅ OK
// person = [30, "Alice"]; // ❌ Error: 順序が違う
// person = ["Alice", 30, true]; // ❌ Error: 決められた長さを超えている
2.2.3.2 オプショナル要素 ❓
要素が存在する場合としない場合があるときは?を使います。ただし、オプショナル要素は必ず末尾に置きます。
let point: [number, number, number?];
point = [10, 20]; // ✅ OK
point = [10, 20, 30]; // ✅ OK
2.2.3.3 残余要素 🌊
タプルの一部だけ型を指定し、残りを可変に扱いたいときに使います。
let logger: [string, ...number[]];
logger = ["Error", 404, 500, 502]; // ✅ 最初は文字列、残りは数値の配列
2.2.3.4 読み取り専用タプル 🔒
タプルは通常の配列と同様にpushやpopが使えるため、固定長が崩れる可能性があります。これを防ぐにはreadonlyを付けます。
const readOnlyTuple: readonly [number, string] = [1, "Fixed"];
// readOnlyTuple.push(2); // ❌ Error: 'readonly'型にはpushメソッドがありません
2.2.3.5 ラベル付きタプル要素 🏷️[推奨]
各要素の意味を名前で示し、可読性を高められます。
type UserInfo = [id: number, name: string, email: string];
const user: UserInfo = [1, "Kim", "kim@example.com"];
2.2.3.6 💡 タプルとインターフェース/オブジェクト ⚖️
データを扱う際にオブジェクト({})とタプル([])で迷ったら、次の基準を参考にしてください。
- オブジェクト: データの意味をキー名で説明する必要がある場合(ほとんどのデータ構造)。
- タプル: 順序自体に意味がある場合や、2~3個の短い値を渡す場合(例:座標
[x, y]、状態管理[state, setter])。
🚀 2.3 追加された型(TypeScript固有のシステム型)
📦 2.3.1 列挙型(Enum)
Enum(列挙型)は、関連項目を1つの集合にまとめて管理するときに使います。
コードの可読性を高め、定義済みの値以外が入らないように制限します。
🚦 2.3.1.1 ステータスコードの管理
代表例として、注文ステータス、ユーザー権限、HTTPレスポンス処理などがあります。
// 1. 宣言:注文ステータスを定義
enum OrderStatus {
Pending = "PENDING", // 保留中
Shipped = "SHIPPED", // 配送中
Delivered = "DELIVERED", // 完了
Cancelled = "CANCELLED" // キャンセル
}
// 2. 使用:関数引数の型として使う
function updateOrderStatus(id: number, status: OrderStatus) {
console.log(`注文番号${id}のステータスを${status}に変更します。`);
}
// 使用例
updateOrderStatus(1024, OrderStatus.Shipped);
// エラー: 定義されていない文字列は指定できません
// updateOrderStatus(1025, "READY");
⚡ 2.3.1.2 const enum
constを付けるとコンパイル時にEnumオブジェクトを作らず、値を直接埋め込むため、生成されるJavaScriptが軽くなります。
const enum Role {
Admin = "ADMIN",
User = "USER"
}
⚖️ 2.3.1.3 数値Enumと文字列Enumの違い
主な違いは、値が自動割り当てされるかと、デバッグ時にどれだけ明確かです。
1️⃣ 値の自動割り当て(Auto-incrementing) * 数値Enum: 値を省略すると0から始まり、1ずつ自動的に増加します。 * 文字列Enum: 自動割り当てはありません。すべてのメンバーに値を手動で指定する必要があります。
// 数値型:0、1、2を自動割り当て
enum Direction { Up, Down, Left }
// 文字列型:必ず明示する
enum DirectionStr {
Up = "UP",
Down = "DOWN",
Left = "LEFT"
}
2️⃣ デバッグとログ(可読性)
* 数値型: ログには0や1が出力され、コードを書いた人でなければ状態を特定しにくくなります。
* 文字列型: ログには"UP"、"DOWN"のような意味の明確なテキストが出力され、DBの確認やエラーログの分析に便利です。
3️⃣ 逆方向マッピング(Reverse Mapping)
* 数値型: 名前から値、値から名前を検索できます(Direction[0] -> "Up")。
* 文字列型: 名前から値のみ検索できます。逆方向マッピングに対応しないため、より安全で厳格です。
🔓 2.3.2 Any(何でも許可)
すべての型チェックを無効にします。便利ですがJavaScriptと変わらなくなり、TypeScriptを使う意味がなくなるため、できる限り避けるべきです。
let value: any = 10;
value = "こんにちは"; // エラーにならない
value.toUpperCase(); // エラーにならない (数値なら実行時エラーの危険)
🔍 2.3.3 Unknown(安全なAny)
anyと同様にどの型の値も格納できますが、使う前に必ず型を確認(型ガード)します。anyよりはるかに安全です。- APIレスポンスのように何が届くか本当に分からない場合に使います。
let value: unknown = "Hello";
// value.toUpperCase(); // エラー! (unknown型なのでそのまま使えない)
if (typeof value === "string") {
value.toUpperCase(); // 型を確認したので使用可能!
}
💨 2.3.4 Void(空)
- 関数が何も返さない場合に使います。
return文がない関数や、コンソールに出力するだけの関数に使います。
function sayHi(): void {
console.log("Hi!");
// return 1; // エラー! 戻り値があってはいけない
}
🚫 2.3.5 Never(決して起こらない)
関数が終了しない、または常にエラーを投げるため、戻り値が存在しない場合に使います。
無限ループや意図的にエラーを発生させる関数に使います。
function throwError(msg: string): never {
throw new Error(msg); // 関数は終了せず、ここで停止する
}
function infiniteLoop(): never {
while (true) {} // 無限に実行される
}
3. インターフェースと型エイリアス 🏷️
展開 / 折りたたみ
3.1 interfaceの基本構造 🏗️
インターフェースはinterfaceキーワードで定義し、オブジェクトが持つべきプロパティとメソッドを明示します。
interface User {
id: number;
name: string;
email: string;
}
const worker: User = {
id: 1,
name: "Kim",
email: "kim@example.com"
};
3.2 オブジェクトの形(Shape)の定義 📐
「オブジェクトの形(Shape)を定義する」とは、どのようなキーを持ち、それぞれにどの型の値を格納するかをあらかじめ決めることです。
3.2.1 設計図(Blueprint)としての役割 📋
建物に設計図が必要なように、コードで「このオブジェクトのidは数値、nameは文字列でなければならない」と定めます。
// 1. interfaceで形(Shape)を定義
interface Product {
name: string;
price: number;
}
// 2. 定義した形に合わせてオブジェクトを作成
const laptop: Product = {
name: "MacBook Pro",
price: 2500000
};
// ❌ エラー:Productにない'color'プロパティや誤った型を検出
const phone: Product = {
name: "iPhone",
price: "150万ウォン", // Error: 'string'は'number'に代入できません
color: "silver" // Error: 'Product'型に'color'はありません
};
3.2.2 「ダックタイピング」と構造的型付け 🦆
TypeScriptは構造的型付け(Structural Typing)を採用しています。名前が異なっても内部構造(Shape)が同じなら同じ型として扱われます。
interface Point {
x: number;
y: number;
}
function printPoint(p: Point) {
console.log(`${p.x}, ${p.y}`);
}
// Pointインターフェースを明示していませんが、形(Shape)が同じなので通ります!
const myObj = { x: 10, y: 20, z: 30 };
printPoint(myObj);
3.3 オプショナル(?)と読み取り専用(readonly)プロパティ 🔒
3.3.1 オプショナルプロパティ(?)❓
プロパティ名の後ろに疑問符(?)を付けます。オブジェクトにそのプロパティがあってもなくてもよい場合に使います。
- 実務での利用: 会員登録時に必須情報(ID、パスワード)と任意情報(自己紹介、WebサイトURL)を区別する際によく使います。
- 特徴: 値を設定しない場合、
undefinedが自動的に割り当てられたものとして扱われます。
interface UserProfile {
id: string; // 必須
nickname: string; // 必須
bio?: string; // 任意(存在する場合としない場合がある)
website?: string; // 任意
}
const user1: UserProfile = {
id: "user01",
nickname: "テコンV"
// bioとwebsiteがなくてもエラーになりません。
};
3.3.2 読み取り専用プロパティ 🛑
プロパティ名の前にreadonlyを付けます。値を代入できるのはオブジェクトの作成時だけで、その後は変更できません。
- 実務での利用: APIキー、一意なID、設定値など、プログラム実行中に変更してはならない重要なデータを保護します。
- 特徴: 実行時ではなくコンパイル時にエラーを検出します。
interface Config {
readonly apiKey: string;
readonly domain: string;
}
const myConfig: Config = {
apiKey: "ABC-12345",
domain: "https://example.com"
};
// ❌ エラー: 読み取り専用プロパティのため変更できません。
myConfig.apiKey = "XYZ-99999";
3.3.3(参考)ReadonlyArray<T>型 📚
- 配列全体を変更できないようにするには
ReadonlyArray<number>を使います。 pushやpopなどの配列変更メソッドを使えないようにします。
const numbers: ReadonlyArray<number> = [1, 2, 3];
// ❌ エラー: 'push'は存在しません
numbers.push(4);
3.4 動的なキーを扱うインデックスシグネチャ 🔑
プロパティ名が事前に分からない場合や、プロパティ数が変動する場合に使う機能です。
3.4.1 基本構文 📝
角括弧[]の中にキーの型と値の型を定義します。
interface SalaryInfo {
// [key: キーの型]: 値の型
[role: string]: number;
}
const teamSalary: SalaryInfo = {
developer: 5000,
designer: 4500,
manager: 5500,
// 値が数値であればどのキーを追加しても通ります。
};
3.4.2 実務で使う理由 💡
- 動的データの処理: ユーザーIDや日付のようにキーが変わるAPIレスポンスを扱う際に不可欠です。
- オブジェクトを辞書のように使う場合: カテゴリーごとのデータの対応付けに便利です。
interface ErrorMessages {
[code: string]: string;
}
const apiErrors: ErrorMessages = {
"404": "ページが見つかりません。",
"500": "サーバーエラーが発生しました。",
"403": "権限がありません。",
};
3.4.3 注意事項とルール ⚠️
- 必須プロパティとの併用: 明示的なプロパティもインデックスシグネチャの規則に従う必要があります。
typescript interface UserInventory { [itemName: string]: number; gold: number; // ✅ 可能(インデックスシグネチャの型と一致) // username: string; // ❌ エラー: インデックスシグネチャがすべての値をnumberに限定しているため } - 複数の型を許可する: 値に複数の型を許可する場合はユニオン型を使います。
typescript interface FlexibleConfig { [key: string]: string | number | boolean; version: number; // ✅ string | number | booleanの範囲内なので可能 } - キーの型: インデックスシグネチャのキー型には、通常
string、number、symbolのみ指定できます。
3.4.4(参考)ユーティリティ型Record<K, T> 🛠️
実務ではインデックスシグネチャを直接書かず、TypeScriptのRecordで簡潔に表現することもあります。
interface Obj { [key: string]: number; }type Obj = Record<string, number>;(上と同じ意味)
3.5 型エイリアス(Type Alias)の使い方 🏷️
型エイリアスは、特定の型やインターフェースに新しい名前(別名)を付ける機能です。
3.5.1 基本的な使い方 📝
オブジェクト型、通常の変数型、ユニオン型など、あらゆる型に名前を付けられます。
// 1. 基本型にエイリアスを付ける
type MyString = string;
const name: MyString = "Kim";
// 2. オブジェクト型を定義(interfaceと同様)
type User = {
id: number;
name: string;
};
const worker: User = { id: 1, name: "Kim" };
3.5.2 interfaceと型エイリアスの比較(Interface vs. Type Alias)🤝
実務でinterfaceではなくtypeを使う主な理由は次の機能です。
3.5.2.1 ユニオン型 🚥
「AまたはB」という型を定義するときに使います(interfaceでは不可)。
type Status = "success" | "error" | "loading";
const currentStatus: Status = "success"; // ✅ OK
const wrongStatus: Status = "pending"; // ❌ エラー: 指定された3つの文字列以外は許可されません
3.5.2.2 タプル 📦
配列の要素数と各位置の型を固定するときに使います。
type Point = [number, number];
const coordinates: Point = [10, 20]; // ✅ OK
3.5.2.3 インターセクション型 ➕
2つ以上の型を1つにまとめるには&演算子を使います。
type Name = { name: string };
type Age = { age: number };
type Person = Name & Age; // { name: string, age: number }と同じ
3.5.2.4 複雑な型の管理 ⚙️
API呼び出しの結果やコンポーネントの状態を直感的に定義できます。
type ResponseData = {
id: number;
content: string;
};
// ユニオン型とオブジェクト型を組み合わせた実務パターン
type ApiResponse =
| { status: "success"; data: ResponseData }
| { status: "error"; message: string };
function handleResponse(response: ApiResponse) {
if (response.status === "success") {
console.log(response.data.content);
} else {
console.log(response.message);
}
}
3.5.3(参考)extendsによる継承 🌲
インターフェースで使う方法で、クラスの継承に似ています。
- 仕組み: 新しいインターフェースが既存のインターフェースのプロパティを継承します。
- 特徴(厳格さ): 親子間でプロパティ型が衝突するとエラーになり、問題を事前に防ぎます。
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// ❌ 衝突時にエラー interface Cat extends Animal { name: number; // Error: 'Animal'の'name(string)'と型が一致しません! }
3.5.4 どちらを使うべき? 🤔
- オブジェクト構造を定義し、継承(
extends)をよく使う場合: →Interface - ユニオン(
|)、インターセクション(&)、基本型エイリアスが必要な場合: →Type Alias
4. 関数 🚀
展開 / 折りたたみ
4.1. 関数型の定義 ✍️
4.1.1. 基本的な関数型の定義
最も基本的で重要な形式です。引数と戻り値の型(Return Type)を明示します。
function add(a: number, b: number): number {
return a + b;
}
// アロー関数形式
const multiply = (a: number, b: number): number => a * b;
4.1.2. オプショナル引数とデフォルト値 ⚙️
関数のパラメーターは?で省略可能にするか、デフォルト値を設定できます。
function greet(name: string, greeting?: string): string {
return `${greeting || 'Hello'}, ${name}`;
}
console.log(greet("太郎")); // "Hello, 太郎"
console.log(greet("花子", "こんにちは")); // "こんにちは, 花子"
4.2. 関数型エイリアス 🏷️
関数自体の型をあらかじめ定義して再利用する方法です。コールバック関数の定義でよく使われます。
type Formatter = (text: string, uppercase: boolean) => string;
const myFormatter: Formatter = (str, isUpper) => {
return isUpper ? str.toUpperCase() : str.toLowerCase();
};
4.3. 関数オーバーロード 🔄
引数の型によって戻り値の型が変わる場合に使います。APIレスポンスの処理やユーティリティ関数の作成に便利です。
// 宣言部:可能な組み合わせを定義
function getValue(v: string): string;
function getValue(v: number): number;
// 実装部:実際のロジック(anyまたはユニオン型を使用)
function getValue(v: any): any {
return v;
}
const str = getValue("ABC"); // string型と推論される
const num = getValue(123); // number型と推論される
4.4. オブジェクトの分割代入 📦
ReactコンポーネントのPropsや複雑なオプションオブジェクトを受け取るときによく使うパターンです。
interface UserConfig {
id: string;
isAdmin: boolean;
}
// 引数部分で型を直接定義するか、interfaceを使う
function loginUser({ id, isAdmin }: UserConfig): void {
console.log(`ID: ${id}, Admin: ${isAdmin}`);
}
4.4.1. なぜこのように書くのか(Before vs. After)🤔
分割代入を使わないと、user.idやuser.isAdminのように毎回オブジェクト名を書く必要があります。
// 分割代入を使わない場合
function loginUser(user: UserConfig) {
console.log(user.id); // 毎回user.を付ける必要がある
console.log(user.isAdmin);
}
// 分割代入を使う場合
function loginUser({ id, isAdmin }: UserConfig) {
console.log(id); // 変数のようにそのまま使える!
console.log(isAdmin);
}
4.4.2. 構文の詳細 🔍
({ id, isAdmin }: UserConfig)を分解すると次のようになります。
{ id, isAdmin }: 「引数のオブジェクトからidとisAdminプロパティを見つけ、同じ名前の変数を作る」という指示です。: UserConfig: 「そのオブジェクトはUserConfig型の規則に従う必要がある」という型定義です。
4.5. アロー関数 🏹
4.5.1. アロー関数
TypeScriptでは、アロー関数の型を定義する方法が2つあります。
4.5.1.1. 関数と同時に型を定義する(最も一般的)
const add = (a: number, b: number): number => a + b;
4.5.1.2. 変数に関数型をあらかじめ指定する(推奨!)✨
type CalculateFn = (a: number, b: number) => number;
const multiply: CalculateFn = (a, b) => a * b; // 引数の型を省略しても自動推論される
4.6. Restパラメーター 🏗️
4.6.1. 引数の数が決まっていないときに使う
TypeScriptではRestパラメーターが必ず配列型である点が重要です。
// ...numbers: number[]は数値だけを含む配列を引数として受け取るという意味。
// : numberは計算後に数値を1つ返すという意味!
function sum(...numbers: number[]): number {
return numbers.reduce((acc, cur) => acc + cur, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(10, 20, 30, 40)); // 100
4.6.2. APIロガー(Rest + 分割代入)🪵
固定の引数と残りのオプションをまとめて扱うときに、この2つの概念を組み合わせます。
interface LogOptions {
level: 'info' | 'error' | 'warn';
silent?: boolean;
}
// 最初の引数はmessageで、残りは複数のオプションオブジェクトの配列として受け取る
const logMessage = (message: string, ...options: LogOptions[]) => {
console.log(`[Message]: ${message}`);
console.log(`[Options]:`, options);
};
logMessage(
"サーバー接続成功",
{ level: 'info' },
{ level: 'warn', silent: true }
);
4.7. コールバック関数の型シグネチャの定義 📞
コールバック関数とは、別の関数に引数として渡される関数です。 TypeScriptでは、コールバックがどの引数を受け取り何を返すかを、シグネチャとしてあらかじめ定義できます。
4.7.1. 基本的な定義方法(インライン)
function processUserInput(name: string, callback: (n: string) => void) {
// 1. nameを処理する
console.log(`ユーザー名:${name}`);
// 2. 処理が終わったらコールバック関数を実行
callback(name);
}
processUserInput("太郎", (userName) => {
console.log(`${userName}さん、ようこそ!`);
});
4.7.2. 型エイリアスを使う方法(実務で推奨)💡
実務ではコールバック型が複雑になることが多いため、別途名前を付けて定義する方法がよく使われます。
// コールバック型を定義(設計図)
type Checker = (id: string, info: { role: string }) => boolean;
// 2. 実行関数:内部でオブジェクトを作り、コールバックに渡す
const run = (id: string, callback: Checker) => {
const isOk = callback(id, { role: 'admin' }); // オブジェクトを渡す!
console.log(isOk ? "承認" : "拒否");
};
// 3. 実際の使用:渡されたオブジェクト(info)を取り出してロジックを作る
run("user01", (id, info) => {
return info.role === 'admin'; // adminならtrueを返す
});
4.7.3. コールバックが省略可能な場合(?)❓
コールバックを渡す場合と渡さない場合があるなら?を付けます。関数内では&&演算子や条件文を使って安全に呼び出すのが定石です。
function fetchData(url: string, onSuccess?: (data: any) => void) {
// ... データを取得するロジック ...
const data = { id: 1, content: "Hello" };
// onSuccessがある場合だけ実行する(&&演算子を利用)
onSuccess && onSuccess(data);
}
4.7.4. 可変引数(...args)を配列型にまとめて処理する 🖇️
// ...tagsの後の:string[]は文字列だけを集めた配列という意味
// 必ずnumber[]やstring[]のような配列型で書きます。
// 必ず最後の引数に1回だけ指定できます。
function makeHashTags(...tags: string[]): string {
// 関数内でmap、filter、reduceなどの配列メソッドをそのまま使えて便利です。
return tags.map(t => `#${t}`).join(' ');
}
// 1個でも3個でも、'tags'という配列として処理される
console.log(makeHashTags("TypeScript")); // "#TypeScript"
console.log(makeHashTags("JS", "TS", "React")); // "#JS #TS #React"
5. クラスとオブジェクト指向 🏗️
展開 / 折りたたみ
TypeScriptのクラスはJavaScriptのクラス構文に型定義とアクセス修飾子を加えたものです。
5.1 クラス使用時の重要なチェックリスト ✅
newキーワード: クラスは設計図にすぎないため、new クラス名()でオブジェクト(インスタンス)を作成して使います。thisの使用: クラス内の変数やメソッドにアクセスするには、必ずthis.を付けます。- 初期化が必須: 宣言だけで値を代入しないとエラーになります(または
name!: stringのような確定代入アサーションを使います)。
5.1.1. 基本的な宣言方法 📝
class Robot {
// 1. メンバー変数の型を宣言
name: string;
model: string;
// 2. コンストラクターを定義
constructor(name: string, model: string) {
this.name = name;
this.model = model;
}
// 3. メソッドを定義
move(): void {
console.log(`${this.name}(${this.model})が動いています。🤖`);
}
}
// 使用方法(インスタンス生成)
const myRobot = new Robot("テコンV", "V1");
myRobot.move(); // "テコンV(V1)が動いています。"
5.1.2. 実務向けの簡潔な宣言 ⚡
実務では上記の方法が煩雑なため、コンストラクター引数にアクセス修飾子(public、privateなど)を付けて、プロパティの宣言と代入を一度に行います。
class User {
// コンストラクターにアクセス修飾子を付けるとメンバー変数の宣言と代入が自動で行われる!
constructor(public nickname: string, private email: string) {}
showInfo() {
console.log(`名前:${this.nickname}、メール:${this.email} 👤`);
}
}
const user = new User("Tipsユーザー", "tip@example.com");
console.log(user.nickname); // ✅ OK (public)
// console.log(user.email); // ❌ エラー!(privateのため外部からアクセス不可)
5.2 3種類のアクセス修飾子 🔐
| 修飾子 | 公開範囲 | 説明 |
|---|---|---|
public |
公開 | どこからでもアクセス可能(デフォルト) |
private |
非公開 | そのクラス内からのみアクセス可能(外部・子クラス不可) |
protected |
保護 | クラス内および継承した子クラスからアクセス可能 |
class Employee {
public name: string; // 誰でも見られる
private salary: number; // 給与は本人(クラス内部)だけが参照できる
protected department: string; // 部署情報は子クラスまで共有
constructor(name: string, salary: number, department: string) {
this.name = name;
this.salary = salary;
this.department = department;
}
public getSalaryInfo() {
return `${this.name}の給与は秘密です。 🤫`; // クラス内部からsalaryにアクセス可能
}
}
class Manager extends Employee {
public changeDept(newDept: string) {
this.department = newDept; // ✅ protectedなので子クラスからアクセス可能!
// this.salary = 0; // ❌ エラー! privateは子クラスからもアクセス不可
}
}
const worker = new Employee("キム太郎", 5000, "開発チーム");
console.log(worker.name); // ✅ OK
// console.log(worker.salary); // ❌ エラー! (外部からアクセス不可)
5.3 privateとJavaScriptの#の違い 🤔
どちらも外部アクセスを防ぎますが、決定的な違いがあります。
- 5.3.1
private(TypeScript構文):- コンパイル時にエラーを検出します。
- JavaScriptに変換されると通常の変数と同じに見えます(実行時の強制力はありません)。
- 5.3.2
#(JavaScript標準構文):- 実行時にも完全に隠されます。
- ブラウザー自体がアクセスを完全に阻止します。
5.4 抽象クラス 🧱
抽象クラスは未完成の設計図です。自らインスタンス化できませんが、子クラスが必ず持つべき共通機能を継承させます。
5.4.1 abstractキーワードで共通の基盤を構築する
抽象クラスには通常のメソッド(実装済み)と抽象メソッド(名前のみ定義)を混在させられます。
abstract class Animal {
// 1. 共通機能(実装済み):子クラスがそのまま使う
breath() {
console.log("息をします。💨");
}
// 2. 抽象メソッド(骨組みのみ):子クラスが必ず自分で実装する
abstract makeSound(): void;
}
class Dog extends Animal {
// abstractメソッドを実装しないとエラー!
makeSound() {
console.log("ワンワン!🐶");
}
}
const myDog = new Dog();
myDog.breath(); // "息をします。" (親の実装を使用)
myDog.makeSound(); // "ワンワン!" (自身の実装を使用)
// const animal = new Animal(); // ❌ エラー! 抽象クラスは直接インスタンス化できません
5.4.2 インターフェース実装(implements)との役割の違い
| 項目 | 抽象クラス(extends) |
インターフェース(implements) |
|---|---|---|
| 役割 | 基本機能を継承させる | 規格・形式に適合させる |
| 実装 | メソッドの実コードを含められる | コードを含められない(宣言のみ) |
| 目的 | 「子クラスとして共通機能を使いなさい」 | 「この機能(メソッド)を必ず持ちなさい」 |
| 複数利用 | 継承できるのは1つだけ | 複数を同時に実装できる |
6. 高度な型 🚀
展開 / 折りたたみ
6.1. ユニオン型(|)🔀
- 「AまたはB」という意味で、複数の型のうちの1つになり得る場合に使います。
- APIレスポンスや状態値の処理によく使われます。
// stringまたはnumberの場合がある
let id: string | number;
id = "user_01"; // ✅ OK
id = 123; // ✅ OK
// 実務例:読み込み状態の処理
type Status = "success" | "error" | "loading";
function printStatus(status: Status) {
console.log(`現在のステータスは${status}です。`);
}
printStatus("success"); // ✅ OK
// printStatus("pending"); // ❌ エラー! (定義済みの文字列以外は拒否)
6.2. インターセクション型(&)🤝
- 「AかつB」という意味です。複数の型を1つにまとめ、すべてのプロパティを持たせます。
- ユーザー情報と権限情報をまとめるときに便利です。
interface Person {
name: string;
}
interface Worker {
employeeId: number;
}
// PersonとWorkerの両方のプロパティが必要
type Employee = Person & Worker;
const newEmployee: Employee = {
name: "パク代理",
employeeId: 9901
};
6.3. 型推論と型アサーション 🔍
6.3.1. 型推論 ✨
- 変数の宣言と同時に値を代入すると、TypeScriptが値から型を自動的に推論します。
- 型が明確な場合は推論に任せるとコードがすっきりします。
let name = "太郎"; // string型と推論される
let age = 25; // number型と推論される
// name = 123; // ❌ エラー! すでにstring型と推論されているため
6.3.2. 型アサーション(as)🛠️
- 開発者が型をより確実に把握している場合に使います。
- キーワード:
as(推奨される形式) - ⚠️ 実際の型が異なると実行時エラーが発生する可能性があるため注意してください。
6.3.2.1. アサーションで具体的なタグを指定する 🏷️
document.querySelectorはデフォルトで広いElement型を返します。特定タグのプロパティ(例:inputのvalue、imgのsrc)を使うにはasで型を絞ります。
// TSは単なるElementと認識しますが、開発者はinputタグだと分かっています。
const myInput = document.getElementById("user-id") as HTMLInputElement;
// アサーションによりvalueプロパティにアクセスできる
console.log(myInput.value);
6.3.2.2. nullチェックのパターン(安全なアクセス)🛡️
要素が画面に存在しない場合に備えます。実務ではオプショナルチェーン(?.)または条件文を必ず使います。
const button = document.querySelector('.submit-btn') as HTMLButtonElement;
// パターンA:オプショナルチェーン(推奨)
button?.click();
// パターンB:条件文(複雑なロジックの場合)
if (button) {
button.disabled = true;
button.innerText = "送信中...";
}
6.3.2.3. イベントリスナーのe.targetの扱い 🎯
イベント関数内でe.targetを使う場合も型アサーションが必要です。TSはtargetがどの要素か判断できないためです。
const handleInput = (e: Event) => {
// e.targetを必要な型としてアサーションして使う
const target = e.target as HTMLInputElement;
console.log(target.value);
};
document.querySelector('#search')?.addEventListener('input', handleInput);
6.3.2.4. よく使われるHTML型の一覧 📋
HTMLInputElement(input)HTMLButtonElement(button)HTMLAnchorElement(aタグ)HTMLImageElement(imgタグ)HTMLFormElement(formタグ)
6.4. 型ガード 👮♂️
型ガードは「型を守る番人」です。ユニオン型のように複数の型が混在するとき、実行時に型を確定し、安全なコードを書けるようにします。
6.4.1. typeof:プリミティブ型の分岐 📝
string、number、booleanなどのプリミティブ型を区別する基本的な方法です。
function printId(id: string | number) {
if (typeof id === "string") {
// このブロック内ではidは確実にstring型です。
console.log(id.toUpperCase());
} else {
// ここでは自動的にnumber型になります。
console.log(id.toFixed(2));
}
}
6.4.2. instanceof:クラスインスタンスの分岐 🏗️
オブジェクトが特定のクラスから作られたか確認するときに使います。
class Dog { bark() { console.log("ワンワン"); } }
class Cat { meow() { console.log("ニャー"); } }
function speak(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark(); // Dog型に確定
} else {
animal.meow(); // Cat型に確定
}
}
6.4.3. ユーザー定義型ガード(isキーワード)👤
複雑なオブジェクトの型を判定する関数を作るときに使います。戻り値の型に引数 is 型と書くのがポイントです。
interface User { name: string; role: string; }
interface Guest { name: string; }
// personがUser型かどうかを確認する関数を定義
function isUser(person: User | Guest): person is User {
return (person as User).role !== undefined;
}
function welcome(person: User | Guest) {
if (isUser(person)) {
// このブロック内ではpersonはUser型として扱われます。
console.log(`管理者の${person.name}さん、ようこそ。`);
} else {
console.log(`訪問者の${person.name}さん、ようこそ。`);
}
}
// 実践例の呼び出し
const admin = { name: "カンチーム長", role: "ADMIN" };
const guest = { name: "匿名" };
welcome(admin); // 出力:「管理者のカンチーム長さん、ようこそ。」
welcome(guest); // 出力:「訪問者の匿名さん、ようこそ。」
// 配列を使った反復処理
const people: (User | Guest)[] = [
{ name: "イ代理", role: "EDITOR" },
{ name: "ギルドン" }
];
people.forEach(p => welcome(p));
7. ジェネリクス 📦
展開 / 折りたたみ
7.1. ジェネリクスの概念 💡
ジェネリクスは、型を変数のように扱う型パラメーターです。関数やクラスの型を事前に固定せず、使用時に型を渡して再利用性を高めます。
7.1.1. なぜジェネリクスが必要なのか 🤔
- [方法1]型ごとに関数を作る(非効率) 😭
typescript function echoNumber(item: number): number { return item; } function echoString(item: string): string { return item; } // 型が増えるたびに関数をコピーし続ける必要がある…… - [方法2]
any型を使う(危険) ❌typescript function echoAny(item: any): any { return item; } const result = echoAny(123); // resultの型がanyになり、数値か文字列か追跡できなくなる -
[方法3]ジェネリクスを使う ✅ ```typescript // はどの型でも入れられる空欄という意味 function echo(item: T): T { return item; }
const r1 = echo(100); // Tはnumberになる const r2 = echo("Hi"); // Tはstringになる ```
7.1.2. 活用例:APIレスポンスの処理 🌐
interface Response<T> {
data: T; // 実データの型はその都度異なる!
status: number;
message: string;
}
// ユーザーデータを受け取る場合
const userRes: Response<{ name: string }> = {
data: { name: "太郎" },
status: 200,
message: "成功"
};
// 商品一覧を受け取る場合
const itemRes: Response<string[]> = {
data: ["りんご", "バナナ"],
status: 200,
message: "成功"
};
7.2. ジェネリック関数とインターフェース 🛠️
7.2.1. ジェネリック関数 ⚙️
関数名の後ろに<T>を付けます。Tは慣例的に使われる名前にすぎません。
// 括弧の前の<T>は、この関数でTという型変数を使う宣言です。
function getFirstElement<T>(arr: T[]): T {
return arr[0];
}
// 使用時に型を指定するか、TSに推論させます。
const str = getFirstElement<string>(["A", "B"]); // Tはstring型
const num = getFirstElement([1, 2, 3]); // Tはnumber型 (自動推論)
7.2.2. テンプレートとしてのジェネリックインターフェース 📑
インターフェース名の後ろに<T>を付けます。データ構造は同じで中の値の型だけが変わる場合に便利です。
// どのような形式のデータ(T)も格納できる箱の設計図
interface Box<T> {
label: string;
content: T;
}
const stringBox: Box<string> = { label: "手紙", content: "こんにちは!" };
const numberBox: Box<number> = { label: "年齢", content: 25 };
7.2.3. HTTPレスポンス処理の例 📡
interface ApiResponse<T> {
success: boolean;
data: T; // ここにどのデータが入るかをジェネリクスで決定!
error?: string;
}
interface User { id: number; name: string; }
function fetchUser(): ApiResponse<User> {
return { success: true, data: { id: 1, name: "太郎" } };
}
const response = fetchUser();
console.log(response.data.name); // User型なので自動補完が可能!
7.2.4. 複数のジェネリック変数を使う(<T, U, K>)🖇️
異なる型を組み合わせたり、型同士の関係を表したりするときに使います。
function merge<T, U>(objA: T, objB: U): T & U {
return { ...objA, ...objB };
}
const combined = merge({ name: '太郎' }, { age: 25 });
console.log(combined.name); // OK
console.log(combined.age); // OK
7.2.5. なぜTやUなのか 📝
慣例として、通常は次の名前を使います。 - T: Type(最初の型) - U: Usually(Tの次の文字) - K: Key - V: Value - E: Element
7.3. ジェネリック制約 ⚓
7.3.1. なぜ制約が必要なのか(問題点)⚠️
<T>だけでは、TypeScriptはどのプロパティがあるか分からずエラーになります。
function printLength<T>(item: T) {
// ❌ エラー: 'T'型に'length'プロパティがありません。
console.log(item.length);
}
7.3.2. extendsで解決する 🤝
「Tは少なくとも特定のプロパティを持つ型でなければならない」という契約を定めます。
interface LengthWise { length: number; }
// Tは必ずLengthWiseインターフェースを満たす必要がある
function printLength<T extends LengthWise>(item: T): void {
console.log(`長さ:${item.length}`); // ✅ これで安心して使えます!
}
printLength("Hello"); // ✅ OK
printLength([1, 2, 3]); // ✅ OK
// printLength(123); // ❌ エラー! (数値にはlengthがない)
7.3.3. 特定のプロパティを抽出する(keyof)🔑
「Kは必ずTのキーのいずれかでなければならない」という制約です。
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const person = { name: "太郎", age: 25 };
getProperty(person, "name"); // ✅ OK
// getProperty(person, "hobby"); // ❌ エラー! (オブジェクトにhobbyキーがない)
8. ユーティリティ型とモジュール(Utility Types & Modules)🛠️
展開 / 折りたたみ
ユーティリティ型は、定義済みの型を状況に応じて変形して使うための型専用ツールです。
8.1. 主なユーティリティ型 🧰
8.1.1. Partial<T>:すべてのプロパティを省略可能にする ❓
既存の型から新しい型を作ります。Partial<User>を使うと、プロパティの一部だけを指定できます。
interface User {
id: number;
name: string;
age: number;
}
function updateUser(id: number, fieldsToUpdate: Partial<User>) {
// 実際の更新ロジック...
}
updateUser(1, { name: "新しい名前" }); // ✅ ageやidがなくてもエラーにならない!
8.1.2. Pick<T, K>:必要なものだけ選ぶ 📍
既存の型から必要なプロパティだけを選んで新しい型を作ります。
// Userからnameとageだけを選んで使いたい場合
type UserPreview = Pick<User, "name" | "age">;
const profile: UserPreview = {
name: "太郎",
age: 20
};
// profile.id; // ❌ エラー! Pickで取得していません
8.1.3. Omit<T, K>:特定のものを除いたすべて ✂️
既存の型から特定のプロパティを除き、残りを使います。
// Userからidだけを除いてすべて使いたい場合
type NewUser = Omit<User, "id">;
const newUser: NewUser = {
name: "花子",
age: 27
// idプロパティを含めると逆にエラーになります!
};
8.1.4. Readonly<T>:読み取り専用にする 🔒
すべてのプロパティを変更できないようにします。
const frozenUser: Readonly<User> = { id: 1, name: "太郎", age: 20 };
// frozenUser.name = "ギルドン"; // ❌ エラー! 読み取り専用です
8.1.5. Record<K, T>:キーと値のオブジェクト型マッピング 🗺️
特定のキー(K)を集めて値(T)の型を固定するときに使い、オブジェクトのマップや辞書の作成に便利です。
8.1.5.1. 基本構文 📝
// Record<キーの型, 値の型>
const scores: Record<string, number> = {
"math": 100,
"english": 90,
};
8.1.5.2. 実務例:固定カテゴリーの管理 🗂️
特定の文字列集合をキーに固定するときに真価を発揮します。
type Page = "home" | "about" | "contact";
interface PageInfo {
title: string;
}
// 「home」「about」「contact」以外のキーは許可されません!
const nav: Record<Page, PageInfo> = {
home: { title: "ホーム" },
about: { title: "紹介" },
contact: { title: "連絡先" },
};
// nav.blog = { title: "ブログ" }; // ❌ エラー!Page型に'blog'キーは存在しない
8.1.5.3. Recordと他のユーティリティ型の組み合わせ 🤝
オブジェクトの規格を明確に定めるツールとして使えます。
// ユーザーID(number)をキー、ユーザー情報(User)を値にするオブジェクト
const userList: Record<number, User> = {
101: { id: 101, name: "太郎", age: 20 },
102: { id: 102, name: "花子", age: 25 },
};
8.2. モジュールシステム 📦
TypeScriptのモジュールシステムは、コードを複数ファイルに分割して共有できる部品管理システムです。
8.2.1. エクスポート 📤
ほかのファイルから使えるように公開する段階です。 - 名前付きエクスポート(Named Export):1つのファイルから複数をエクスポートするとき - デフォルトエクスポート(Default Export):ファイルの代表となる項目を1つだけエクスポートするとき
// math.ts
export const PI = 3.14; // Named Export
export default function add(a: number, b: number) { // Default Export
return a + b;
}
8.2.2. インポート 📥
ほかのファイルにある部品を取り込む段階です。
// app.ts
import add, { PI } from './math'; // Defaultは波括弧なし、Namedは波括弧内に!
console.log(add(1, 2)); // 3
console.log(PI); // 3.14
8.2.3. 型専用モジュール(import type)🏗️
実行時データではなく、型という設計図だけをインポートするときに使います。
// types.ts
export interface User { id: number; name: string; }
// main.ts
import type { User } from './types'; // 「型だけを使う」と明示
const user: User = { id: 1, name: "太郎" };
9. tsconfig.jsonの主要オプション
展開 / 折りたたみ
tsconfig.jsonはTypeScriptのコンパイル環境を設定する重要なファイルです。実務でよく調整する主要な5つのオプションは次のとおりです。
9.1. 主なコンパイルオプション
target: TypeScriptをどのバージョンのJavaScriptに変換するか決めます(例:ES6、ESNext)。module: モジュールシステムを決めます。最新のフロントエンドツールでは主にESNext、Node.jsではCommonJSを使います。strict: すべての型チェックオプションを有効にします。実務ではtrueを強く推奨します。outDir: コンパイル結果(.js)を保存するフォルダーを指定します。paths: プロジェクト内の絶対パス(例:@/components/...)を使えるようにして可読性を高めます。
9.2. 実務向けの推奨設定例
{
"compilerOptions": {
"target": "ESNext", // 最新のJSバージョンに変換
"module": "ESNext", // 最新のモジュールシステムを使用
"strict": true, // 厳格な型チェックを有効化(必須)
"outDir": "./dist", // ビルド結果をdistフォルダーに保存
"rootDir": "./src", // ソースコードはsrcフォルダーから開始
"baseUrl": ".", // パス解決の基準点
"paths": {
"@/*": ["src/*"] // @/記号でsrcフォルダーへのアクセスを許可
},
"esModuleInterop": true, // CommonJSライブラリとの互換性を有効化
"skipLibCheck": true, // ライブラリの型チェックを省略(ビルド速度を向上)
"forceConsistentCasingInFileNames": true // ファイル名の大文字・小文字の区別を強制
},
"include": ["src/**/*"], // srcフォルダー内のすべてのファイルを含める
"exclude": ["node_modules"] // チェック対象から除外するフォルダー
}
9.3. まとめ
- 設計:
targetとmoduleで出力の互換性を決めます。 - 安全性:
strictで実行時エラーを防ぎます。 - 構造:
rootDir、outDir、pathsでプロジェクト構造を管理します。