跳至主要内容

[TS] Classes

  • Field and Methods:在 Class 中,帶一般值的「屬性」稱作 field;帶一般值的「函式」稱作 method。

Class Members

fields and initializers

  • define fields:class 被初始化時,這些 fields 會一併被建立,目的是要讓 TS 知道這個 class 中有哪些 fields,及其 modifiers(預設是 public
  • initializers:為這個 fields 給予初始值
  • 透過 strictPropertyInitialization 的設定選項,可以控制 class fields 是否需要在 constructor 中 被初始化
class Contact {
// STEP 1 - define fields: 定義這個 class 中有的 fields,及其 modifiers(預設是 public)
// STEP 2 - initializers: 為這個 fields 給予初始值
email: string = 'pjchender@gmail.com';
public name: string = 'pjchender'; // public 可以不要寫
protected idNumber: string = 'A123456789';
private passwordSeed: number = 1;
}
備註

如果在 class 中使用了沒有被定義到的 fields,會出現類似 "Property 'name' does not exist on type 'Contact'" 的錯誤。

constructor

  • 如果需要從外部帶入參數,可以在 constructor 中取得
class Contact {
// STEP 1 - define fields: 定義這個 class 中有的 fields,及其 modifiers(預設是 public)
// STEP 2 - initializers: 為這個 fields 給予初始值
email: string = 'pjchender@gmail.com';
public name: string = 'pjchender'; // public 可以不要寫
protected idNumber: string = 'A123456789';
private passwordSeed: number = 1;

// STEP 3: 如果需要從外部帶入參數,可以在 constructor 中取得
constructor(idNumber: string, name: string, email: string = 'no email') {
this.idNumber = idNumber;
this.name = name;

// 如果沒寫這行,則會直接用 initializers 中的值("pjchender@gmail.com")
// 而不是 constructor 中的 defaultValue("no-email")
this.email = email;
}
}

const c = new Contact('A123456789', 'pjchender');
console.log(c.email); // "no email"

但要在 STEP 1 先定義 fields,接著重複在 STEP 3 的 constructor 中賦值是一件有點多餘的是,因此如果這個 fields 會同時在 constructors 中出現是,我們可以省略 fields 的定義:

  • 對於會在 constructor 中出現的 fields,可以不用在上方先定義其 fields
  • 在 constructor 中可以什麼都不用寫,TS 會自己執行 this.xxx = xxx 的程式
class Contact {
private passwordSeed: number = 1;

// 對於會在 constructor 中出現的 fields,可以不用在上方先定義其 fields
constructor(
protected idNumber: string = 'A123456789',
public name: string = 'pjchender',
public email: string = 'no email',
) {
// TS 會自己執行 this.xxx = xxx 的程式,因此我們不需要在做些什麼
}
}

const c = new Contact();
console.log(c.name); // "pjchender"
備註

如果省略 define fields 的步驟,直接在 constructor 中帶入時,如果是 public 的 fields 也要明確定義。

Definite Assignment Assertions

Definite Assignment Assertions @ TS v2.7

如果在 define fields 的後面加上驚嘆號(!),意思是我會自己負責這個 field 被初始化,請 TS 不要管:

let x!: number; // x 一定會被我初始化,請 TS 不用管這個錯誤

class Contact {
// token 一定會被我初始化,請 TS 不用管這個錯誤
private token!: string;
}

Class Field Type Inference

Class Property Inference from Constructors @ frontend-master

在 TypeScript 4 之後,如果不指定 field 的型別,TypeScript 會自動使用 constructor 中給的型別來推論該型別。

危險

記得要將 noImplicitAny 設定為 true 才能。

例如,原本要在每個 fields 後定義各 fields 的型別:

class Color {
private red: number;
protected green: number;
public blue: number; // public 可以不用寫
rgb: string;

constructor(c: [number, number, number]) {
const [r, g, b] = c;
this.red = r;
this.green = g;
this.blue = b;
this.rgb = `rgb(${r}, ${g}, ${b})`;
}
}

const lightCoral = new Color([240, 128, 128]);

現在可以讓 TypeScript 自動根據 constructors 中被賦予的值來推論:

 class Color {
- private red: number;
+ private red;

- protected green: number;
+ protected green;

- public blue: number; // public 可以不用寫
+ public blue; // public 可以不用寫

- rgb: string;
+ rgb;

constructor(c: [number, number, number]) {
const [r, g, b] = c;
this.red = r;
this.green = g;
this.blue = b;
this.rgb = `rgb(${r}, ${g}, ${b})`;
}
}

Implement

interface HasEmail {
email: string;
name: string;
}

export class Contact implements HasEmail {
email: string;
name: string;
constructor(email: string, name: string) {
this.email = email;
this.name = name;
}
}

Inheritance

  • 預設的情況下,若子類別沒有使用 constructor 這個關鍵字它會自動繼承父層的 constructor
class Square {
constructor(public width: number) {}
}

// 子類別沒有使用 constructor 關鍵字的時候,會自動繼承父層的 constructor
class Rectangle extends Square {}

const square = new Square(10);
const rectangle = new Rectangle(20);

console.log(square); // { width: 10 }
console.log(rectangle); // { width: 20 }

若子類別有使用 constructor ,則需要使用 super 來表示複層的 constructor:

class Square {
constructor(public width: number) {}
}

// 子類別「有」使用 constructor 關鍵字的時候,需要透過 super 來啟動父層的 constructor
class Rectangle extends Square {
constructor(width: number, public height: number) {
super(width);
}
}

const square = new Square(10);
const rectangle = new Rectangle(10, 20);

console.log(square); // { width: 10 }
console.log(rectangle); // { width: 10, height: 20 }

Modifier

  • Public(default): everyone,在類別中或透過實例(instance)都可以呼叫此方法,沒有特別宣告的話,預設就是 public
  • Protected:me and subclasses,該 class 及繼承該 class 的其他類別內都可以呼叫此方法,但實例不能
  • Private:only me,只能在該 class 內使用此方法,實例不能

Methods Modifiers

class Vehicle {
// Methods
public honk(): void {
console.log('beep');
}

private drive(): void {
console.log('vroom');
}

// protected 的方法可以透過繼承給其他 class 呼叫
protected woo(): void {
console.log('woo');
}
}

const vehicle = new Vehicle();
vehicle.honk(); // public method 可以透過實例呼叫
vehicle.drive(); // 錯誤,private method 不可透過實例呼叫
vehicle.woo(); // 錯誤,protected method 不可透過實例呼叫

Access Modifiers

public

// 寫法一:需要使用 this.height = height
class Square {
width: number = 20;
height: number;

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

// 寫法二:使用 public height 後可以省略許多
class Square {
width: number = 20;
constructor(public height: number) {}
}

const square = new Square(40); // { width: 20, height: 40 }

private

在實例中將無法存取 private 的屬性:

// 對 width 和 height 都添加 private modifier
class Square {
private width: number = 20;

constructor(private height: number) {
// height 可以被當成參數傳入,但不能被 instance 存取
}
}

const square = new Square(40);
console.log(square); // 可以存取,{ width: 20, height: 40 }
console.log(square.width); // 錯誤發生!private 屬性不能直接透過 instance 存取

Class 同時是 value 也同時是 type

比較特別的地方是,在 TS 中,定義了一個 class 表示同時定義了 value 和 type:

class Person {
firstName: string = '';
lastName: string = '';
static createMock() {
return {
firstName: 'Aaron',
lastName: 'Chen',
};
}
}

const P = Person; // 這裡的 Person 是 value 不是 type,而 P 的型別會是 "typeof Person"
P.createMock();

const p = new Person(); // p 的型別是 "Person",所以這裡的 "Person" 是 type
備註

簡單來說,class constructor 本身的型別會是 typeof ClassName,而該 class 產生的 instance 的型別才會是 ClassName 本身。

其他

any constructor type

如果要定義 any 的 class constructor 的 type definition 會像是這樣:

type AnyConstructor = { new (...args: any): any };