TypeScript | 関数・クラス・ジェネリクス:クラス設計 – メソッドの型指定

TypeScript TypeScript
スポンサーリンク

ゴール:「メソッドの型だけ見て“何をするか”がだいたい分かるように書ける」

クラスのメソッドって、書こうと思えば何でも書けます。
だからこそ「型をどう付けるか」で、読みやすさと安全性が大きく変わります。

ここでは、

  • メソッドの基本的な型指定
  • this を意識した設計
  • 戻り値の型の決め方
  • コールバックやジェネリクスを使うメソッドの型

を、順番にかみ砕いていきます。


メソッドの基本的な型指定

まずは「普通の関数」と同じだと思っていい

クラスのメソッドは、基本的には「クラスの中に書く関数」です。
なので、型の付け方も関数と同じです。

class Calculator {
  add(a: number, b: number): number {
    return a + b;
  }

  log(message: string): void {
    console.log("[LOG]", message);
  }
}

const c = new Calculator();
const result = c.add(1, 2); // result: number
c.log("計算しました");
TypeScript

ここで大事なのは、

  • 引数に型を付ける
  • 戻り値にも型を付ける

この2つを「必ず書く」習慣をつけることです。

戻り値の型を省略しても TypeScript は推論してくれますが、
「このメソッドは何を返す前提なのか」を明示したほうが、
読む人にも未来の自分にも親切です。

戻り値の型を“意識して”決める

例えば、ユーザーを更新するメソッドを考えます。

class User {
  constructor(
    public name: string
  ) {}

  rename(newName: string): void {
    this.name = newName;
  }
}
TypeScript

void にしたのは、

  • 「このメソッドは“状態を変えるだけ”で、値は返さない」

という設計の意思表示です。

一方で、メソッドチェーンをしたいなら、
自分自身を返す設計もできます。

class User {
  constructor(
    public name: string
  ) {}

  rename(newName: string): this {
    this.name = newName;
    return this;
  }
}

const u = new User("Taro").rename("Jiro").rename("Saburo");
TypeScript

ここでのポイントは、

「このメソッドを呼んだあと、呼び出し側は何を受け取れると嬉しいか?」

を考えて、戻り値の型を決めることです。


this とメソッド:インスタンスの状態をどう扱うか

this は「そのインスタンス自身」

クラスのメソッドの中で this を使うと、
そのインスタンスのプロパティにアクセスできます。

class User {
  constructor(
    public name: string
  ) {}

  greet(): void {
    console.log(`こんにちは、${this.name}です`);
  }
}

const u = new User("Taro");
u.greet(); // こんにちは、Taroです
TypeScript

ここで this.name は、
「このメソッドを呼んでいるインスタンスの name」です。

メソッドの型を考えるときは、

  • 引数:外から渡される情報
  • this:インスタンスがすでに持っている情報
  • 戻り値:呼び出し側に返す情報

という3つの視点で整理すると、設計しやすくなります。

this を戻り値に使うパターン

さきほど少し出した「メソッドチェーン」のパターンは、
戻り値の型に this を使うのがポイントです。

class Builder {
  private text = "";

  append(str: string): this {
    this.text += str;
    return this;
  }

  build(): string {
    return this.text;
  }
}

const b = new Builder();
const result = b.append("Hello, ").append("World").build();
TypeScript

append の戻り値を this にすることで、

  • append を何度もつなげて呼べる
  • 最後に build() で結果を取り出せる

というインターフェースになります。

「このメソッドは“自分自身を返すと便利そうか?”」
という視点を持っておくと、this を戻り値に使うタイミングが見えてきます。


メソッドでコールバックを受け取るときの型

コールバックの型を“その場で書かない”

例えば、「配列の各要素に対して処理をする」メソッドをクラスで持つとします。

class NumberList {
  constructor(
    public values: number[]
  ) {}

  forEach(callback: (value: number, index: number) => void): void {
    this.values.forEach(callback);
  }
}
TypeScript

これでも動きますが、
(value: number, index: number) => void が少し読みにくいですよね。

こういうときは、コールバックの型に名前をつけてあげると読みやすくなります。

type NumberCallback = (value: number, index: number) => void;

class NumberList {
  constructor(
    public values: number[]
  ) {}

  forEach(callback: NumberCallback): void {
    this.values.forEach(callback);
  }
}
TypeScript

メソッドの型を読むときに、

  • 引数:callback: NumberCallback
  • 戻り値:void

と、一瞬で理解できます。

「メソッドの引数に関数を渡す」場面では、
その関数の型を型エイリアスに切り出す癖をつけると、
クラスの宣言がかなりスッキリします。

ジェネリクス+コールバックのメソッド

もう少しだけ進んだ例も見ておきます。

class List<T> {
  constructor(
    public values: T[]
  ) {}

  map<U>(fn: (value: T, index: number) => U): U[] {
    return this.values.map(fn);
  }
}

const list = new List([1, 2, 3]);
const doubled = list.map(n => n * 2); // number[]
const asString = list.map(n => `#${n}`); // string[]
TypeScript

ここでのポイントは、

  • クラス自体がジェネリクス T を持っている
  • map メソッドはさらに U という型パラメータを持つ
  • fn の型は (value: T, index: number) => U
  • 戻り値は U[]

という関係になっていることです。

「メソッドの型にジェネリクスを使う」ときは、

  • メソッドが「どんな型を受け取って」
  • 「どんな型に変換して」
  • 「何を返すのか」

を、型パラメータで素直に表現してあげると、
読みやすく・使いやすいメソッドになります。


メソッドの戻り値設計:void / 値 / this / Promise

void にするのは「副作用だけ」のとき

例えば、ログを出すだけのメソッド。

class Logger {
  log(message: string): void {
    console.log(message);
  }
}
TypeScript

これは「呼び出し側に返すべき値は特にない」ので、void が自然です。

ただし、「本当に何も返さない設計でいいのか?」は一度考えてみてください。

例えば、成功/失敗を返したいなら、
戻り値を変える選択肢もあります。

class Logger {
  log(message: string): boolean {
    try {
      console.log(message);
      return true;
    } catch {
      return false;
    }
  }
}
TypeScript

値を返すメソッド:計算・変換・取得

「何かを計算して返す」「状態を元に値を返す」メソッドは、
積極的に戻り値の型を付けます。

class Rectangle {
  constructor(
    public width: number,
    public height: number
  ) {}

  area(): number {
    return this.width * this.height;
  }

  isSquare(): boolean {
    return this.width === this.height;
  }
}
TypeScript

ここでは、

  • areanumber
  • isSquareboolean

という「メソッドの性格」が、型からも伝わります。

非同期メソッド:Promise<…> を戻り値にする

API 呼び出しなど、非同期処理をするメソッドは async を付けます。

class UserService {
  async fetchUser(id: number): Promise<User> {
    const res = await fetch(`/users/${id}`);
    const data = await res.json();
    return data as User;
  }
}
TypeScript

async を付けると、戻り値は必ず Promise<…> になります。

ここで大事なのは、

  • 「このメソッドは“結果をすぐ返す”のか、“あとで返す(Promise)”のか」
  • 呼び出し側は await する前提なのか

を、戻り値の型でハッキリさせることです。


まとめ:メソッドの型指定を自分の言葉で整理すると

最後に、あなた自身の言葉でこう整理してみてください。

メソッドの型を設計するときは、

  • 引数の型と戻り値の型を必ず書く(推論に甘えすぎない)
  • this が「インスタンスの状態」を表すことを意識する
  • 戻り値は void / 値 / this / Promise<…> のどれにするかを意図して選ぶ
  • コールバックを受け取るときは、その関数型に名前をつけてあげる
  • ジェネリクスを使うときは「何を何に変換するメソッドか」を型パラメータで素直に表す

今書いているクラスからメソッドを1つ選んで、

「このメソッド、本当は何を返すべき?」
「引数と戻り値の型だけ見て、役割が伝わる?」

と問い直してみてください。

メソッドの型は、
“そのクラスの API の顔”です。
そこを丁寧に設計していくと、クラス全体の見通しが一気によくなります。

タイトルとURLをコピーしました