在 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 屬性。
類別繼承有下面幾個重點:
子類別可以新增自己的屬性和方法。
子類別可以覆寫父類別的方法,以擴展或修改成自身的方法。
一個子類別可以同時繼承多個父類別。
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 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 類別的物件作為參數,這使我們可以使用這個函式來處理不同動物的名字和叫聲。
泛型類別在創建通用或可重用的程式碼,主要優勢包括:
重複使用性: 我們可以編寫一次通用的程式碼,以處理多種不同類型的數據。
型別安全性: 泛型類別會檢查是否正確使用了類型,並防止不同類型的混合使用。
程式碼整潔性: 泛型類別可以減少程式碼,使程式碼更加整潔和易於維護。
下面是泛型類別的範例:
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 來組合不同的接口,使一個類別具有多種方法。