[TS] Classes
- Classes @ ts-docs
- TypeScript 3 Fundamentals @ Frontend Masters
- 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 };