本文 AI 產出,尚未審核

TypeScript – 類別(Classes)

class 宣告與成員


簡介

在 JavaScript 進入 ES6 後,class 成為語言的原生語法,讓物件導向(OOP)程式設計變得更直觀。TypeScript 進一步在此基礎上加入 型別系統修飾子(modifier)抽象類別 等功能,使得大型專案的結構更清晰、錯誤更容易在編譯階段被捕捉。

本單元將說明 class 的宣告方式、成員的種類與存取控制,並透過實務範例展示如何在 TypeScript 中寫出安全、可維護的類別。無論是剛接觸 JavaScript 的新手,或是已有 JavaScript 背景想升級到 TypeScript 的開發者,都能從這篇文章獲得實用的知識。


核心概念

1. 基本的 class 宣告

在 TypeScript 中,class 的語法與 ES6 幾乎相同,只是可以在屬性與方法上直接加上型別註記。

class Person {
  // 成員變數(屬性)必須先宣告型別
  name: string;
  age: number;

  // 建構子(constructor)負責初始化屬性
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  // 方法(member function)
  greet(): void {
    console.log(`Hello, I'm ${this.name}, ${this.age} years old.`);
  }
}

重點:在 TypeScript 中,屬性若未指定存取修飾子,預設為 public,即外部可以直接讀寫。


2. 存取修飾子(public、private、protected、readonly)

修飾子 可見範圍 典型用途
public 任意位置(預設) API 介面、外部可直接使用的屬性
private 只在 class 本身內部可見 隱藏實作細節,避免外部誤用
protected 本身與子類別可見 供繼承使用的共用屬性或方法
readonly 只能在宣告或建構子中賦值 常數屬性、不可變資料
class BankAccount {
  // 只能在建構子或宣告時賦值,之後不可變
  readonly accountNumber: string;
  // 僅在本類別內部使用,外部無法直接存取
  private balance: number = 0;

  constructor(accountNumber: string) {
    this.accountNumber = accountNumber;
  }

  // 公開的存款方法,內部會修改 private 成員
  deposit(amount: number): void {
    if (amount <= 0) throw new Error('Amount must be positive');
    this.balance += amount;
  }

  // 只給予讀取餘額的權限,避免外部直接改寫
  getBalance(): number {
    return this.balance;
  }
}

3. 參數屬性(Parameter Properties)

TypeScript 允許在建構子參數前直接加上修飾子,省去先宣告再賦值的步驟。

class Point {
  // 直接在參數前加上 public,會自動產生對應的屬性
  constructor(public x: number, public y: number) {}
}

// 使用
const p = new Point(10, 20);
console.log(p.x, p.y); // 10 20

技巧:若屬性僅在建構子內使用且不需要外部存取,可使用 privateprotected,同時保持程式碼簡潔。


4. 靜態成員(static)

static 成員屬於類別本身,而非實例。常用於 常數、工具方法共享狀態

class MathUtil {
  // 靜態常數
  static readonly PI = 3.14159;

  // 靜態方法
  static areaOfCircle(radius: number): number {
    return MathUtil.PI * radius * radius;
  }
}

// 呼叫方式不需要建立實例
console.log(MathUtil.areaOfCircle(5)); // 78.53975

5. 抽象類別與抽象方法(abstract)

抽象類別無法直接實例化,只能被繼承。抽象方法則必須在子類別中實作。

abstract class Animal {
  // 抽象方法,子類別必須實作
  abstract makeSound(): void;

  // 具體方法,所有子類別皆可共用
  move(): void {
    console.log('Moving...');
  }
}

class Dog extends Animal {
  makeSound(): void {
    console.log('Woof!');
  }
}

const d = new Dog();
d.makeSound(); // Woof!
d.move();      // Moving...

6. 介面(interface)與類別的結合

TypeScript 允許類別 實作(implements)介面,以保證類別具備介面所定義的結構。

interface ILogger {
  log(message: string): void;
  error(message: string): void;
}

class ConsoleLogger implements ILogger {
  log(message: string): void {
    console.log(`[INFO] ${message}`);
  }
  error(message: string): void {
    console.error(`[ERROR] ${message}`);
  }
}

常見陷阱與最佳實踐

陷阱 說明 解決方案 / 最佳實踐
忘記加修飾子 屬性預設為 public,不小心把本應私有的資料暴露。 主動在屬性前寫上 privateprotected,讓意圖更明確。
在建構子外直接改寫 readonly TypeScript 允許在任何地方改寫 readonly(除非使用 --noImplicitOverride)。 使用 readonly 搭配 private,或在 tsconfig 開啟 exactOptionalPropertyTypes 以加強檢查。
靜態與實例屬性混淆 this.xxx 讀取 static 成員會出錯。 使用類別名稱(如 ClassName.xxx)存取靜態成員。
抽象類別未實作所有抽象方法 編譯器會報錯,但若使用 any 逃過檢查,會在執行時出現 undefined 嚴格模式strict:true)下,編譯器會強制檢查。
過度使用 any 失去 TypeScript 型別保護的優勢。 盡量使用具體型別,或在不確定時使用 unknown 再做型別斷言。

其他最佳實踐

  1. 使用 private / protected 隱藏實作細節,只暴露必要的 API。
  2. 依功能分層:將純資料結構放在 DTO(Data Transfer Object)類別,將業務邏輯放在 Service 類別。
  3. 遵循 SOLID 原則,尤其是 單一職責原則(SRP),避免類別過於龐大。
  4. 配合 Lint(如 ESLint + @typescript-eslint),自動檢測未使用的 public 成員或不必要的 any
  5. 使用 readonly 讓不可變的屬性在編譯階段即被保護,減少 bug。

實際應用場景

1. 前端 UI 元件(React + TypeScript)

在大型 React 專案中,使用 class 來封裝 UI 元件的狀態與行為,配合 interface 定義 props,能讓組件的 API 更清晰。

import React from 'react';

interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;
}

class Button extends React.Component<ButtonProps> {
  // 使用 readonly 表示 props 不會在組件內被改寫
  readonly props: ButtonProps;

  render() {
    const { label, onClick, disabled } = this.props;
    return (
      <button onClick={onClick} disabled={disabled}>
        {label}
      </button>
    );
  }
}

2. 後端服務(Node.js + TypeScript)

在建立 RESTful API 時,以 class 表示資料模型與商業邏輯,搭配 private 資料庫連線,確保外部無法直接存取。

import { Pool } from 'pg';

class UserRepository {
  private readonly pool: Pool;

  constructor(pool: Pool) {
    this.pool = pool;
  }

  async findById(id: number) {
    const res = await this.pool.query('SELECT * FROM users WHERE id = $1', [id]);
    return res.rows[0];
  }

  // 其他 CRUD 方法...
}

3. Game 開發(Entity Component System)

在遊戲開發中,每個遊戲實體(Entity)可用 class 表示,並透過繼承或組合方式擴充功能

abstract class GameObject {
  constructor(public x: number, public y: number) {}
  abstract update(delta: number): void;
}

class Player extends GameObject {
  private speed: number = 5;

  update(delta: number): void {
    // 依據使用者輸入更新位置
    this.x += this.speed * delta;
  }
}

總結

  • TypeScript 的 class 在保留 JavaScript 原生語法的同時,提供 型別、修飾子、抽象 等強大功能,使得物件導向程式設計更安全、可維護。
  • 透過 public / private / protected / readonly 以及 static抽象類別,開發者可以清楚劃分 API 與實作細節。
  • 參數屬性介面實作抽象方法 等語法讓類別的宣告更加簡潔且具表意性。
  • 在實務上,無論是前端 UI、後端服務或是遊戲開發,正確運用 class 能提升程式碼可讀性、降低錯誤率,並且與 TypeScript 的型別系統相輔相成,讓大型專案更易於管理與擴充。

掌握了 class 宣告與成員 的核心概念後,接下來可以進一步探索 繼承、介面合併(mixins) 以及 Decorator 等進階特性,讓你的 TypeScript 程式碼更具彈性與表現力。祝開發順利!