iT邦幫忙

2023 iThome 鐵人賽

DAY 22
0

在 React v16.8 之前,React 需要使用 Class 來撰寫,這也讓很多人怯步,但從 React v16.8 推出了 React Hooks 後 ( 2019 年 ),可能使用 Class 的機會好像比較少。但人生嘛,總是有機會遇到舊專案或是需要改寫的時候,所以還是需要暸解一下 Class 的架構以及 TypeScript 在 Class 上的用法。

Class 是物件導向 ( OOP ) 中的核心概念之一,是 ECMAScript 6 ( ES2015 ) 新增的,它使用 屬性、建構函式和方法 三個元素所組成,讓我們可以定義一個具有相關屬性和方法的物件模型,這些屬性和方法描述了該物件的特性和行為,並可以通過繼承來建立 Class 相互之間的關係,簡單來說就是創造新物件的藍圖。

  • 屬性 ( Properties ): 描述了物件的特性或資訊。例如,一個使用者類別可以有姓名和年齡等屬性。

  • 建構函式 ( Constructor ): 用於初始化物件的屬性。將參數賦值給屬性。

  • 方法 ( Methods ): 類別中的函式,用於定義物件的行為。例如,一個使用者類別有一個打招呼方法。

以下是一個簡單的範例:

class User {
  // 屬性
  name: string;
  age: number;

  // 建構函式
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  // 方法
  greet(): string {
    return `哈囉,我是${this.name},今年 ${this.age} 歲。`;
  }
}

const willPig = new User("威爾豬", 3);
const willSheep = new User("威爾羊", 10);

console.log(willPig.greet()); // 輸出: 哈囉,我是威爾豬,今年 3 歲。
console.log(willSheep.greet()); // 輸出: 哈囉,我是威爾羊,今年 10 歲。

在這個例子中,我們定義了一個 User 類別,並在建構函式中初始化 name 和 age 兩個屬性。

我們再看另一個範例:

class Rectangle {
  width: number;
  height: number;

  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }

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

const rectangle1 = new Rectangle(10, 5);
const rectangle2 = new Rectangle(3, 8);

console.log(rectangle1.getArea()); // 輸出: 50
console.log(rectangle2.getArea()); // 輸出: 24

在這個例子中,我們定義了一個 Rectangle 類別,並在建構函式中初始化 width 和 height 屬性。

繼承 ( Inheritance )

類別繼承有下面幾個重點:

  • 子類別可以新增自己的屬性和方法。

  • 子類別可以覆寫父類別的方法,以擴展或修改成自身的方法。

  • 一個子類別可以同時繼承多個父類別。

  • super() 用於呼叫父類別的建構函式,以確保父類別的初始化工作正確執行。

也就是我們創建新的類別時,可以從現有的一個或多個類別繼承其屬性和方法,以實現程式碼的重用和擴展 ( 使用 extends 來繼承,並使用 super() 來呼叫父類別的建構函式 )。當子類別繼承父類別的方法時,可以選擇是否要重寫該方法,以便於在子類別中定義自身的方法。

以下是基本的繼承的範例:

// 父類別
class Animal {
  name: string;

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

  makeSound(): void {
    console.log("叫聲");
  }
}

// 子類別
class Sheep extends Animal {
  constructor(name: string) {
    // 呼叫父類別的建構函式
    super(name);
  }

  makeSound(): void {
    console.log(`${this.name}咩咩叫`);
  }
}

const mySheep = new Sheep("旺財");
mySheep.makeSound(); // 輸出: 旺財咩咩叫

在這個例子中,我們定義了一個 Animal 類別,包含 name 屬性和 makeSound 方法。然後,我們創建了一個 Sheep 類別,使用 extends 關鍵字來繼承 Animal 類別的屬性和方法。我們在 Sheep 類別的建構函式中使用 super() 來呼叫父類別的建構函式,並且重寫了 makeSound 方法。

但因為我們屬性並沒有設定 類別修飾符,所以預設會是 public 的狀態,其實可以直接使用父類別的屬性,不需要創建建構函示來使用 super() 呼叫父類別的建構函示,修改如下:

class Animal {
  name: string;

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

  makeSound(): void {
    console.log("叫聲");
  }
}

class Sheep extends Animal {
  makeSound(): void {
    console.log(`${this.name}咩咩叫`);
  }
}

const mySheep = new Sheep("旺財");
mySheep.makeSound(); // 輸出: 旺財咩咩叫

多重繼承

在某些情況下,我們可能希望一個子類別繼承自多個父類別的屬性和方法,就像我們可能同時想要一個金主爸爸和一個超人爸爸等。不過 TypeScript 不支援這種方式的多重繼承,但是我們可以使用 接口 interface 來實現類似於多重繼承的效果,這種方式稱為 混入 ( Mixins ),混入允許一個類別繼承多個接口,每個接口都可以定義屬性和方法,通過實現多個接口 ( 使用 implements 來實現 ),一個類別就可以具有多種不同的屬性和方法。

以下是使用接口實現類似於多重繼承的範例:

interface Money {
  money(): void;
}

interface Power {
  power(): void;
}

class Man {
  name: string;

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

  bodyguard(): void {
    console.log(`${this.name}有保鑣`);
  }
}

// 使用接口實現多重繼承
class TopMan extends Man implements Money, Power {
  money() {
    console.log(`${this.name}有錢`);
  }

  power() {
    console.log(`${this.name}有權`);
  }
}

const king = new TopMan("國王");
king.money(); // 輸出: 國王有錢
king.power(); // 輸出: 國王有權
king.bodyguard(); // 輸出: 國王有保鑣

在這個例子中,我們定義了兩個接口 Money 和 Power,分別描述了金錢和權力的行為。然後,我們創建了子類別 TopMan 來繼承父類別 Man,並實現兩個不同的接口。這樣,我們可以根據需要組合不同的行為,實現類似於多重繼承的效果。

抽象類別 ( Abstract Class )

抽象類別 僅作為父類別來使用,它不能直接實體化,但可以被其他類別來繼承,通常用於 定義共用的屬性和方法

注意:子類別必須實現抽象類別中的所有抽象方法

以下是抽象類別的範例:

// 抽象類別
abstract class Shape {
  // 抽象方法
  abstract getArea(): number;
}

// 繼承抽象類別
class Circle extends Shape {
  radius: number;

  constructor(radius: number) {
    super();

    this.radius = radius;
  }

  getArea() {
    return Math.round(Math.PI * this.radius ** 2);
  }
}

// 繼承抽象類別
class Rect extends Shape {
  width: number;
  height: number;

  constructor(width: number, height: number) {
    super();

    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

const circle = new Circle(10);
const rect = new Rect(10, 5);

console.log(circle.getArea()); // 輸出: 314
console.log(rect.getArea()); // 輸出: 50

在這個例子中,我們定義了一個抽象類別 Shape,其中包含一個抽象方法 getArea,然後我們創建了兩個子類別 Circle 和 Rect 來繼承 Shape,並分別實現 getArea 方法。

我們再看另一個範例:

abstract class Animal {
  name: string;

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

  abstract makeSound(): string;
}

class Sheep extends Animal {
  makeSound() {
    return "咩咩叫";
  }
}

class Cat extends Animal {
  makeSound() {
    return "喵喵叫";
  }
}

const handleSound = (animal: Animal): void => {
  console.log(`${animal.name}${animal.makeSound()}`);
};

const mySheep = new Sheep("旺財");
const myCat = new Cat("旺妞");

handleSound(mySheep); // 輸出: 旺財咩咩叫
handleSound(myCat); // 輸出: 旺妞喵喵叫

在這個例子中,我們定義了一個抽象類別 Animal,其中包含一個抽象方法 makeSound。然後,我們創建了兩個子類別 Sheep 和 Cat 來繼承 Animal,並在子類別分別實現了 makeSound 方法。最後,我們定義了一個 handleSound 函式,它接受一個 Animal 類別的物件作為參數,這使我們可以使用這個函式來處理不同動物的名字和叫聲。

泛型類別 ( Generic Class )

泛型類別在創建通用或可重用的程式碼,主要優勢包括:

  • 重複使用性: 我們可以編寫一次通用的程式碼,以處理多種不同類型的數據。

  • 型別安全性: 泛型類別會檢查是否正確使用了類型,並防止不同類型的混合使用。

  • 程式碼整潔性: 泛型類別可以減少程式碼,使程式碼更加整潔和易於維護。

下面是泛型類別的範例:

class MyArray<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  getItem(index: number): T | undefined {
    if (index >= 0 && index < this.items.length) {
      return this.items[index];
    }
    
    return undefined;
  }
}

const stringArray = new MyArray<string>();
stringArray.push("威爾豬");
stringArray.push("威爾羊");
console.log(stringArray.getItem(0)); // 輸出: 威爾豬

const numberArray = new MyArray<number>();
numberArray.push(1);
console.log(numberArray.getItem(3)); // 輸出: undefined

在這個範例中,我們定義了一個 MyArray<T> 泛型類別,當我們創建 stringArray 時,它將處理字串類型的數據,而 numberArray 則處理數字類型的數據。這樣就可以讓我們使用相同的程式碼來處理不同類型的數據,同時保持類型安全性。


總之,我們可以使用類別來定義物件的屬性和方法,並透過繼承來建立類別之間的關係,使用抽象類別來定義基礎框架,並在子類別中實現特定的行為,而接口則提供了一種靈活的方式來實現類似於多重繼承的效果,通過 implements 來組合不同的接口,使一個類別具有多種方法。


上一篇
泛型(Generics)
下一篇
類別封裝 ( Class Encapsulation )
系列文
用不到 30 天學會基本 TypeScript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言