# typescript 11 ## 11-2 使用函式建構子 跳過 ## 11-3 使用類別 ```typescript= interface Person { id: string name: string city: string } class Employee { // 使用 class 的時候物件的屬性需要宣告 id: string name: string dept: string city: string constructor (id: string, name: string, dept: string, city: string) { this.id = id this.name = name this.dept = dept this.city = city } writeDept () { console.log(`${this.name} works in ${this.dept}`) } } const salesEmployee = new Employee('fvega', 'Fidel Vega', 'Sales', 'Paris') const data: (Person | Employee)[] = [ { id: 'bsmith', name: 'Bob Smith', city: 'London' }, { id: 'ajones', name: 'Alice Jones', city: 'Paris' }, { id: 'dpeters', name: 'Dora Peters', city: 'New York' }, salesEmployee ] data.forEach(item => { if (item instanceof Employee) { item.writeDept() } else { console.log(`${item.id} ${item.name}, ${item.city}`) } }) ``` ## 11-3-2 屬性的存取控制關鍵字 public (預設) private protected ```typescript= class Employee { public id: string public name: string private readonly dept: string public city: string constructor (id: string, name: string, dept: string, city: string) { this.id = id this.name = name this.dept = dept this.city = city } writeDept () { console.log(`${this.name} works in ${this.dept}`) } } const salesEmployee = new Employee('fvega', 'Fidel Vega', 'Sales', 'Paris') // src/index.ts(21,38): error TS2341: Property 'dept' is private and only accessible within class 'Employee'. console.log(`Dept is ${salesEmployee.dept}`) ``` ## 11-3-3 定義唯讀屬性 ```typescript= class Employee { public readonly id: string public name: string private readonly dept: string public city: string constructor (id: string, name: string, dept: string, city: string) { this.id = id this.name = name this.dept = dept this.city = city } writeDept () { console.log(`${this.name} works in ${this.dept}`) } } const salesEmployee = new Employee('fvega', 'Fidel Vega', 'Sales', 'Paris') // src/index.ts(20,15): error TS2540: Cannot assign to 'id' because it is a read-only property. salesEmployee.id = 'fidel' ``` ## 11-3-4 進一步簡化類別建構子 ```typescript= class Employee { constructor ( public readonly id: string, public name: string, private readonly dept: string, public city: string) { } writeDept () { console.log(`${this.name} works in ${this.dept}`) } } ``` ## 11-3-5 使用類別繼承 關鍵字 extends super ```typescript= class Employee extends Person { constructor (public readonly id: string, public name: string, private readonly dept: string, public city: string) { super(id, name, city) // 呼叫父類別的建構式 } writeDept () { console.log(`${this.name} works in ${this.dept}`) } } ``` ## 11-3-6 瞭解子類別的型別推論 如果你放任編譯器自行推論類別子類別的型別,很容易產生預期之外的結果 ```typescript= class Person { constructor (public id: string, public name: string, public city: string) { } } class Employee extends Person { constructor (public readonly id: string, public name: string, private readonly dept: string, public city: string) { super(id, name, city) } writeDept () { console.log(`${this.name} works in ${this.dept}`) } } class Customer extends Person { constructor (public readonly id: string, public name: string, public city: string, public creditLimit: number) { super(id, name, city) } } class Supplier extends Person { constructor (public readonly id: string, public name: string, public city: string, public companyName: string) { super(id, name, city) } } // 初始化的時候只有包含 Employee 和 Customer 所以編譯器會自行推論出 data 的型別爲 // declare const data: (Employee | Customer)[]; const data = [ new Employee('fvega', 'Fidel Vega', 'Sales', 'Paris'), new Customer('ajones', 'Alice Jones', 'London', 500) ] data.push(new Supplier('dpeters', 'Dora Peters', 'New York', 'Acme')) data.forEach(item => { console.log(`Person: ${item.name}, ${item.city}`) if (item instanceof Employee) { item.writeDept() } else if (item instanceof Customer) { console.log(`Customer ${item.name} has ${item.creditLimit} limit`) } else if (item instanceof Supplier) { // src/index.ts(43,11): error TS2345: Argument of type 'Supplier' is not assignable to parameter of type 'Employee | Customer'. // Property 'creditLimit' is missing in type 'Supplier' but required in type 'Customer'. // src/index.ts(51,14): error TS2358: The left-hand side of an 'instanceof' expression must be of type 'any', an object type or a type parameter. // src/index.ts(52,34): error TS2339: Property 'name' does not exist on type 'never'. // src/index.ts(52,57): error TS2339: Property 'companyName' does not exist on type 'never' console.log(`Supplier ${item.name} works for ${item.companyName}`) } }) ``` 修正: ```typescript= const data: Person[] = [ new Employee('fvega', 'Fidel Vega', 'Sales', 'Paris'), new Customer('ajones', 'Alice Jones', 'London', 500) ] ``` ## 11-4-1 定義抽象類別 (abstract class) ```typescript= // 抽象類別 abstract class Person { constructor (public id: string, public name: string, public city: string) { } getDetails (): string { return `${this.name}, ${this.getSpecificDetails()}` } // 抽象方法 // 可以強迫子類別實作抽象方法 abstract getSpecificDetails (): string } ``` ## 11-4-2 對抽象類別使用型別防衛敘述 ```typescript= abstract class Person { constructor (public id: string, public name: string, public city: string) { } getDetails (): string { return `${this.name}, ${this.getSpecificDetails()}` } abstract getSpecificDetails (): string } class Employee extends Person { constructor (public readonly id: string, public name: string, private readonly dept: string, public city: string) { super(id, name, city) } getSpecificDetails () { return `works in ${this.dept}` } } class Customer extends Person { constructor (public readonly id: string, public name: string, public city: string, public creditLimit: number) { super(id, name, city) } getSpecificDetails () { return `has ${this.creditLimit} limit` } } class Supplier { constructor (public readonly id: string, public name: string, public city: string, public companyName: string) { } } const data: (Person | Supplier)[] = [ new Employee('fvega', 'Fidel Vega', 'Sales', 'Paris'), new Customer('ajones', 'Alice Jones', 'London', 500), new Supplier('dpeters', 'Dora Peters', 'New York', 'Acme') ] // data.forEach(item => console.log(item.getDetails())); data.forEach(item => { if (item instanceof Person) { console.log(item.getDetails()) } else { console.log(`${item.name} works for ${item.companyName}`) } }) ``` ## 11-5-1 定義與實作界面 界面與抽象類別的差異: 1. 界面不實作方法 2. 界面不定義建構子,只定義物件的形狀 ```typescript= interface Person { name: string getDetails: () => string } class Employee implements Person { constructor (public readonly id: string, public name: string, private readonly dept: string, public city: string) { } getDetails () { return `${this.name} works in ${this.dept}` } } ``` ## 11-5-2 同時實作多個界面 ```typescript= interface Person { name: string getDetails: () => string } interface DogOwner { dogName: any getDogDetails: () => string } class Customer implements Person, DogOwner { constructor (public readonly id: string, public name: string, public city: string, public creditLimit: number, public dogName) { } getDetails () { return `${this.name} has ${this.creditLimit} limit` } getDogDetails () { return `${this.name} has a dog named ${this.dogName}` } } ``` 實作多個界面的前提是各個界面的同名屬性不能有相斥的型別 譬如 ```typescript= interface Person { id: string } interface DogOwner { id: integer } ``` ## 11-5-3 界面繼承 ```typescript= interface Person { name: string getDetails: () => string } // 界面也可以使用 extends 關鍵字 interface DogOwner extends Person { dogName?: string getDogDetails?: () => string } class Employee implements Person { constructor (public readonly id: string, public name: string, private readonly dept: string, public city: string) { } getDetails () { return `${this.name} works in ${this.dept}` } } ``` ## 11-5-4 在界面定義選擇性的屬性與方法 ```typescript= // ? interface DogOwner extends Person { dogName?: string getDogDetails?: () => string } dogOwners.forEach(item => { // 執行時要檢查是否有實作選擇性的屬性與方法,避免發生錯誤 if (item.getDogDetails) { console.log(item.getDogDetails()) } }) ``` ## 11-5-5 定義一個實作界面的抽象類別 ```typescript= interface Person { name: string getDetails: () => string dogName?: string getDogDetails?: () => string } abstract class AbstractDogOwner implements Person { abstract name: string abstract dogName?: string abstract getDetails () getDogDetails () { if (this.dogName) { return `${this.name} has a dog called ${this.dogName}` } } } class DogOwningCustomer extends AbstractDogOwner { constructor (public readonly id: string, public name: string, public city: string, public creditLimit: number, public dogName) { super() } getDetails () { return `${this.name} has ${this.creditLimit} limit` } } ``` ## 11-5-6 對介面使用型別防衛敘述 因爲 JS 沒有介面相關的功能,所以 TS 編譯產生出來的程式碼也不會保留介面相關的資訊 所以我們無法使用 instanceof 關鍵字來檢查 ```typescript= data.forEach(item => { // 不過我們可以檢查介面獨有的成員是否存在 if ('getDetails' in item) { console.log(`Person: ${item.getDetails()}`) } else { console.log(`Product: ${item.name}, ${item.price}`) } }) ``` ## 11-6 動態建立屬性 JS: ```javascript= const a = {} a.new_attr = 2 // ok ``` TS: ```typescript= const a = {} // src/index.ts(2,3): error TS2339: Property 'new_attr' does not exist on type '{}'. a.new_attr = 2 ``` 解決方式: ```typescript= interface Product { name: string price: number } class SportsProduct implements Product { constructor (public name: string, public category: string, public price: number) { } } class ProductGroup { [propertypName: string]: Product; } const group = new ProductGroup() group.shoes = new SportsProduct('Shoes', 'Running', 90.50) group.hat = new SportsProduct('Hat', 'Skiing', 20) Object.keys(group).forEach(k => { console.log(`Property Name: ${k}`) }) ```