
TypeScriptのクラス入門
class・constructor・継承の基本
TypeScriptのクラスの基本を初心者向けに解説。classの書き方、constructor、public/private、継承の仕組みまでシンプルに理解できます。
全8回シリーズ — 目次
1 TypeScriptとは
2 環境構築
3 基本の型
4 関数と型
5 インターフェイス
6 クラス
7 実践的な型
8 便利機能まとめ
🎯 この記事のゴール
TypeScriptのクラス構文を理解し、アクセス修飾子・継承・implements を使ったクラスベース設計ができるようになること。JavaやPHPの経験者には馴染み深い内容で、TypeScript独自の機能も合わせて学びます。🔍 クラスを使うべきかどうか
TypeScriptでは、クラスよりも関数と interface の組み合わせで設計できる場面が多くあります。Reactなどのフロントエンドフレームワークはほぼクラスを使いません。一方、NestJSやORMライブラリ(TypeORM / Prismaなど)はクラスを多用します。「クラスが必要な場面で正しく使える」ことが目標です。 実行ファイルを用意しよう
Bunの場合
touch classes.ts
bun --watch run classes.ts Node.js + npm の場合
touch src/classes.ts
npx tsc --watch # 別ターミナルで起動
node dist/classes.js クラスの基本 — プロパティと constructor
JavaScriptのクラスはES2015から導入されましたが、TypeScriptではプロパティや引数に型を付けられます。クラスを定義するとき、 プロパティの型宣言 を先頭に書くのが TypeScript 流です。
class User {
// ① プロパティの型宣言(先頭にまとめて書く)
id: number;
name: string;
email: string;
// ② constructor で初期化
constructor(id: number, name: string, email: string) {
this.id = id;
this.name = name;
this.email = email;
}
// ③ メソッドにも戻り値の型を付ける
greet(): string {
return `こんにちは、${this.name}さん(ID: ${this.id})`;
}
toString(): string {
return `[User] ${this.name} <${this.email}>`;
}
}
const alice = new User(1, "Alice", "alice@example.com");
console.log(alice.greet()); // → こんにちは、Aliceさん(ID: 1)
console.log(alice.toString()); // → [User] Alice <alice@example.com> Bunの場合
bun run classes.ts
#こんにちは、Aliceさん(ID: 1)
#[User] Alice <alice@example.com> Node.js + npm の場合
npx tsc && node dist/classes.js
#こんにちは、Aliceさん(ID: 1)
#[User] Alice <alice@example.com> アクセス修飾子 — public / private / protected
TypeScriptのクラスでは、プロパティやメソッドに アクセス修飾子 を付けて「どこからアクセスできるか」を制限できます。
public
どこからでもアクセス可能。デフォルト値なので省略しても同じ。明示的に書くと意図が伝わりやすい。
private
クラスの内部からのみアクセス可能。外部から直接変更させたくないデータに使う。
protected
クラス自身と継承したサブクラスからアクセス可能。継承を前提とした設計で使う。
class BankAccount {
public owner: string; // 外部からアクセスOK
private balance: number; // クラス内部のみ
constructor(owner: string, initialBalance: number) {
this.owner = owner;
this.balance = initialBalance;
}
// public メソッドを通じて private な残高を操作する
public deposit(amount: number): void {
if (amount <= 0) throw new Error("入金額は0より大きい値にしてください");
this.balance += amount;
}
public withdraw(amount: number): void {
if (amount > this.balance) throw new Error("残高不足です");
this.balance -= amount;
}
// 残高は「見る」だけできる getter
public getBalance(): number {
return this.balance;
}
}
const account = new BankAccount("Alice", 10000);
account.deposit(5000);
account.withdraw(3000);
console.log(account.owner); // → Alice
console.log(account.getBalance()); // → 12000
// private プロパティへの直接アクセスはエラー
console.log(account.balance); // エラー: 'balance' は private プロパティ Bunの場合
bun run classes.ts
#Alice
#12000 Node.js + npm の場合
npx tsc && node dist/classes.js
#Alice
#12000 コンストラクタ省略形(TypeScript の便利構文)
TypeScript ではconstructor の引数にアクセス修飾子を付けると、プロパティ宣言と初期化を同時に行えます。コードが大幅にすっきりします。
// 通常の書き方(冗長)
class PointVerbose {
public x: number;
public y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
// ↓ コンストラクタ省略形(引数に修飾子を付けるだけ)
class Point {
constructor(public x: number, public y: number) {}
distanceTo(other: Point): number {
return Math.sqrt(
(this.x - other.x) ** 2 + (this.y - other.y) ** 2
);
}
toString(): string {
return `(${this.x}, ${this.y})`;
}
}
const p1 = new Point(0, 0);
const p2 = new Point(3, 4);
console.log(p1.toString()); // → (0, 0)
console.log(p1.distanceTo(p2)); // → 5 Bunの場合
bun run classes.ts
#(0, 0)
#5 Node.js + npm の場合
npx tsc && node dist/classes.js
#(0, 0)
#5✨ 実務での使い分け
シンプルなデータクラスにはコンストラクタ省略形が便利です。一方、初期化ロジックが複雑な場合(バリデーション・変換処理など)は通常の書き方のほうが読みやすくなります。 継承 — extends で機能を引き継ぐ
extends を使うと親クラスのプロパティ・メソッドをすべて引き継いだ子クラスを作れます。super() で親クラスの constructor を呼び出す必要があります。
Animal(親クラス)
プロパティ
protected name : string
メソッド
public move (distance: number ): void
public toString ( ): string
↓ extends
Dog(子クラス)
追加メソッド
public bark (): void
Bird(子クラス)
追加メソッド
public fly (): void
class Animal {
// protected: 子クラスからもアクセスできる
constructor(protected name: string) {}
move(distance: number = 0): void {
console.log(`${this.name} が ${distance}m 移動した`);
}
toString(): string {
return `Animal(${this.name})`;
}
}
class Dog extends Animal {
constructor(name: string, private breed: string) {
super(name); // 親クラスのコンストラクタを呼ぶ(必須)
}
bark(): void {
console.log(`${this.name}(${this.breed}): ワンワン!`);
}
// 親メソッドをオーバーライド
override move(distance: number = 0): void {
console.log(`${this.name} が走って ${distance}m 移動した`);
}
}
class Bird extends Animal {
constructor(name: string) {
super(name);
}
fly(altitude: number): void {
console.log(`${this.name} が ${altitude}m の高さを飛んでいる`);
}
}
const dog = new Dog("ポチ", "柴犬");
const bird = new Bird("ピーコ");
dog.bark(); // → ポチ(柴犬): ワンワン!
dog.move(20); // → ポチ が走って 20m 移動した(オーバーライド後)
bird.fly(100); // → ピーコ が 100m の高さを飛んでいる
bird.move(10); // → ピーコ が 10m 移動した(親クラスのまま) Bunの場合
bun run classes.ts
#ポチ(柴犬): ワンワン!
#ポチ が走って 20m 移動した
#ピーコ が 100m の高さを飛んでいる
#ピーコ が 10m 移動した Node.js + npm の場合
npx tsc && node dist/classes.js
#ポチ(柴犬): ワンワン!
#ポチ が走って 20m 移動した
#ピーコ が 100m の高さを飛んでいる
#ピーコ が 10m 移動した✨ override キーワード
TypeScript 4.3以降ではoverride キーワードを付けることで「親クラスのメソッドを意図的に上書きしている」と明示できます。親クラスにそのメソッドが存在しない場合はコンパイルエラーになるため、リファクタリング時の事故を防げます。 implements — interface との組み合わせ
クラスにimplements を付けると、そのクラスが指定した interface の「契約」を必ず実装していることを TypeScript が保証します。 「何ができるか」を interface で定義し、「どうやるか」をクラスで実装する のが設計の基本です。
// 「通知を送る」という契約(インターフェース)
interface Notifier {
send(to: string, message: string): void;
name: string;
}
// メール通知の実装
class EmailNotifier implements Notifier {
name = "Email";
send(to: string, message: string): void {
console.log(`[Email → ${to}] ${message}`);
}
}
// Slack 通知の実装
class SlackNotifier implements Notifier {
name = "Slack";
send(to: string, message: string): void {
console.log(`[Slack → #${to}] ${message}`);
}
}
// interface 型で受け取れば、実装の詳細に依存しない
function notifyAll(notifiers: Notifier[], message: string): void {
notifiers.forEach((n) => {
n.send("admin", `[${n.name}] ${message}`);
});
}
notifyAll(
[new EmailNotifier(), new SlackNotifier()],
"デプロイが完了しました"
);
// implements を付けても必要なメソッドがないとエラー
class BrokenNotifier implements Notifier {
name = "Broken";
// send メソッドがない → コンパイルエラー
} Bunの場合
bun run classes.ts
#[Email → admin] [Email] デプロイが完了しました
#[Slack → #admin] [Slack] デプロイが完了しました Node.js + npm の場合
npx tsc && node dist/classes.js
#[Email → admin] [Email] デプロイが完了しました
#[Slack → #admin] [Slack] デプロイが完了しました クラス関連キーワード 早見表
| キーワード | 意味 | 主な使いどころ |
|---|---|---|
| public | どこからでもアクセス可 | 外部に公開するプロパティ・メソッド(デフォルト) |
| private | クラス内部のみアクセス可 | 残高・パスワードなど外部から直接変更させたくないデータ |
| protected | クラス本体 + 継承クラスのみ | 子クラスで使う共通プロパティ |
| readonly | 初期化後は変更不可 | IDなど変わってほしくない値 |
| extends | 親クラスを継承 | 共通機能を持つ基底クラスから派生させるとき |
| implements | interface の契約を実装 | 「何ができるか」を保証したいとき |
| override | 親メソッドを意図的に上書き | 子クラスで親メソッドの動作を変えるとき |
| super() | 親クラスのコンストラクタを呼ぶ | extends しているクラスの constructor で必須 |
🔍 クラスを使うべきかどうか
TypeScriptでは、クラスよりも関数と interface の組み合わせで設計できる場面が多くあります。Reactなどのフロントエンドフレームワークはほぼクラスを使いません。一方、NestJSやORMライブラリ(TypeORM / Prismaなど)はクラスを多用します。「クラスが必要な場面で正しく使える」ことが目標です。📌 第6回のまとめ
クラスの基本構文・コンストラクタ省略形・アクセス修飾子(public /private /protected )・継承(extends )・interface との組み合わせ(implements )を学びました。次回は Union型・literal型・型ガード・型推論といった「TypeScriptらしい書き方」を一気に学ぶ実践回です。📝 ▶ 次のステップ
次は「 」を解説します