# TypeScript使用泛型 #### 泛用型別(泛型) - 在定義函式或類別的時候先不預先決定好具體的型別,等到被呼叫時再視傳入資料而定 #### 單一型別 ```ts= export class Person { constructor(public name: string, public age: number) {} } ``` - DataCollection類別只能拿來管理Person型別的物件 ```ts= import { Person, Product } from "./dataType.js"; let peolple = [ new Person("John", 25), new Person("Jane", 22), ]; class DataCollection { private items: Person[] = []; constructor(initialItems: Person[]) { this.items.push(...initialItems); } getNames(): string[] { return this.items.map((item) => item.name); } getItem(index: number): Person { return this.items[index]; } } let data = new DataCollection(peolple); console.log(data.getNames().join(", ")); // John, Jane let firstData = data.getItem(0); console.log(`First data: ${firstData.name}, ${firstData.age}`); // First data: John, 25 ``` #### 支援第二種型別 - 利用TypeScript的型別聯集 ```ts= let peolple = [ new Person("John", 25), new Person("Jane", 22), ]; let products = [ new Product("Phone", 699), new Product("Tablet", 899), ]; type dataType = Person | Product; // Here class DataCollection { private items: dataType[] = []; // Here constructor(initialItems: dataType[]) { this.items.push(...initialItems); } getNames(): string[] { return this.items.map((item) => item.name); } getItem(index: number): dataType { // Here return this.items[index]; } } let data = new DataCollection(products); console.log(data.getNames().join(", ")); // Phone, Tablet let firstData = data.getItem(0); if (firstData instanceof Person) { console.log(`First data: ${firstData.name}, ${firstData.age}`); } else if (firstData instanceof Product) { console.log(`First data: ${firstData.name}, ${firstData.price}`); } // First data: Phone, 699 ``` -- 需要使用防禦型別 #### 泛型 ```ts= class DataCollection<T> { private items: T[] = []; constructor(initialItems: T[]) { this.items.push(...initialItems); } // 由於T不知道會是哪種型別,所以無法存取類別內的name屬性 -> 使用型別防禦敘述 getNames(): string[] { return this.items.map(item =>{ if (item instanceof Person || item instanceof Product) { return item.name; } else { return null; } } ) } getItem(index: number): T { return this.items[index]; } } let data = new DataCollection<Person>(people); console.log(data.getNames().join(", ")); let firstData = data.getItem(0); console.log(`First data: ${firstData.name}, ${firstData.age}`) ``` #### 限制泛型可用型別 - 限制在Person與Product時,就可以確保Name的屬性 ```ts= class DataCollection<T extends (Person | Product)> { private items: T[] = []; constructor(initialItems: T[]) { this.items.push(...initialItems); } getNames(): string[] { return this.items.map(item => item.name); } getItem(index: number): T { return this.items[index]; } } ``` #### 以物件形狀來限制泛型型別 ```ts= class DataCollection<T extends { name: string}> { private items: T[] = []; constructor(initialItems: T[]) { this.items.push(...initialItems); } getNames(): string[] { return this.items.map(item => item.name); } getItem(index: number): T { return this.items[index]; } } ``` #### 在類別內定義多個泛型參數 ```ts= let people = [ new Person("John", "Paris"), new Person("Jane", "London"), ]; let cities = [ new City("London", 8136000), new City("Paris", 2244000), ]; class DataCollection<T extends { name: string}, U> { private items: T[] = []; constructor(initialItems: T[]) { this.items.push(...initialItems); } // 找到交集 collate(targetData: U[],itemProp: string, targetProp: string): (T & U)[] { let result = []; this.items.forEach(item => { let match = targetData.find(datum => datum[targetProp] === item[itemProp]); if (match !== undefined) { result.push({...match ,...item}); } }); return result; } getNames(): string[] { return this.items.map(item => item.name); } getItem(index: number): T { return this.items[index]; } } let peopleData = new DataCollection<Person,City>(people); // 傳入cities陣列,比對People的city和City的name屬性 let collatedData = peopleData.collate(cities, "city", "name"); collatedData.forEach(item => console.log(`${item.name} lives in ${item.city} which has a population of ${item.population}`)); // John lives in Paris which has a population of 2244000 // Jane lives in London which has a population of 8136000 ``` #### 將泛型參數套用至物件方法 - 在建立DataCollection物件時,就指名collate()方法要用的資料型別 -> 彈性不夠 ```ts= let peopleData = new DataCollection<Person,City>(people); ``` - 將泛型參數套用在物件方法上 ```ts= // 不在這裡套用U class DataCollection<T extends { name: string}> { private items: T[] = []; constructor(initialItems: T[]) { this.items.push(...initialItems); } // 套用在這裡 collate<U>(targetData: U[],itemProp: string, targetProp: string): (T & U)[] { let result = []; this.items.forEach(item => { let match = targetData.find(datum => datum[targetProp] === item[itemProp]); if (match !== undefined) { result.push({...match ,...item}); } }); return result; } getNames(): string[] { return this.items.map(item => item.name); } getItem(index: number): T { return this.items[index]; } } ``` ```ts= // 不需要設定U let peopleData = new DataCollection<Person>(people); // 產生物件後才套入City let collatedData = peopleData.collate<City>(cities, "city", "name"); ``` #### 允許編譯器推論型別參數 ```ts= let peopleData = new DataCollection(people); let collatedData = peopleData.collate(cities, "city", "name"); collatedData.forEach(item => console.log(`${item.name} lives in ${item.city} which has a population of ${item.population}`)); ``` #### 泛型類別的繼承 - SearchableCollection<T>使用extends繼承自DataCollection<T> - 子類別的泛型參數必須和父類別相容 ```ts= class DataCollection<T extends { name: string}> { // 為了給子層使用而改成protected protected items: T[] = []; constructor(initialItems: T[]) { this.items.push(...initialItems); } } class SearchableCollection<T extends { name: string }> extends DataCollection<T> { constructor(initialItems: T[]) { super(initialItems); } find(name: string): T { return this.items.find(item => item.name === name); } } let peopleData = new SearchableCollection<Person>(people); let foundPerson = peopleData.find("Jane"); if (foundPerson) { console.log(`Person: ${foundPerson.name}, ${foundPerson.city}`); } ``` #### 子類別鎖定泛型參數 - 子類別定義只能用來操作父類別泛型參數中的其中一種型別,子類別可以拋棄泛型,在繼承時就指定型別參數 ```ts= class SearchableCollection extends DataCollection<Person> { constructor(initialItems: Person[]) { super(initialItems); } find(city: string): Person { return this.items.find(item => item.city === city); } } let peopleData = new SearchableCollection(people); let foundPerson = peopleData.find("London"); if (foundPerson) { console.log(`Person: ${foundPerson.name}, ${foundPerson.city}`); } ``` #### 限制子類別的泛型參數範圍 ```ts= class SearchableCollection <T extends Employee | Person> extends DataCollection<T> { constructor(initialItems: T[]) { super(initialItems); } find(searchTerm: string): T[]{ return this.items.filter(item => { if (item instanceof Employee) { return item.name === searchTerm || item.role === searchTerm; } else if (item instanceof Person) { return item.name === searchTerm || item.city === searchTerm; } }); } } let employeeData = new SearchableCollection<Employee>(employees); employeeData.find("Sales").forEach(e => console.log(`Employee ${e.name} works in ${e.role}`)); // Employee John works in Sales ``` #### 對泛型類別使用型別謂詞函式 ```ts= class DataCollection<T> { protected items: T[] = []; constructor(initialItems: T[]) { this.items.push(...initialItems); } filter<V extends T>(predicate: (target) => target is V): V[] { // 接收一個型別謂詞函式來過濾物件型別,並以V[]型別回傳 return this.items.filter(i => predicate(i)) as V[]; } } let mixedData = new DataCollection<Person | Product>([...people, ...products]); // 定義型別謂詞函式 function isProduct(target): target is Product { return target instanceof Product; } // 把型別謂詞函式傳入filter() let filteredProducts = mixedData.filter<Product>(isProduct); filteredProducts.forEach(p => console.log(`Product: ${p.name}, ${p.price}`)); ``` - 型別謂詞函式isProduct()會被傳給filter()的參數predicate,而且函式的定義也符合要求 #### 在泛型類別定義一個靜態方法 ```ts= class DataCollection<T> { protected items: T[] = []; constructor(initialItems: T[]) { this.items.push(...initialItems); } filter<V extends T>(predicate: (target) => target is V): V[] { return this.items.filter(i => predicate(i)) as V[]; } // 靜態方法不可以填入型別,因為只能透過類別本身存取 static reverse(items: any[]){ return items.reverse(); } } let mixedData = new DataCollection<Person | Product>([...people, ...products]); function isProduct(target): target is Product { return target instanceof Product; } let filteredProducts = mixedData.filter<Product>(isProduct); filteredProducts.forEach(p => console.log(`Product: ${p.name}, ${p.price}`)); // 在呼叫靜態方法時,類別名稱並不需要加上泛型參數 let reversedProducts: Product[] = DataCollection.reverse(filteredProducts); reversedProducts.forEach(p => console.log(`Product: ${p.name}, ${p.price}`)); // Product: Phone, 699 // Product: Tablet, 899 // Product: Tablet, 899 // Product: Phone, 699 ``` - 靜態方法本身能定義自己的泛型參數 ```ts= static reverse<ArrayType>(items: ArrayType[]){ return items.reverse(); } let reversedProducts: Product[] = DataCollection.reverse<Product>(filteredProducts); ``` #### 定義泛型介面 - Collection<T>介面擁有一個名為T的泛型參數,而且T型別必須擁有string型別的name屬性 - T這個型別參數被用在add()與get()方法中,實作介面的類別可以沿用T泛型參數 ```ts= type shapType = { name: string }; interface Collection<T extends shapType>{ add(...newItems: T[]): void; get(name: string): T; count: number; } ``` #### 泛型介面的繼承 ```ts= interface Collection<T extends shapType>{ add(...newItems: T[]): void; get(name: string): T; count: number; } // 沿用相同的泛型型別 interface SearchableCollection<T extends shapType> extends Collection<T>{ find(name: string): T | undefined; } // 鎖定泛型型別 interface ProductCollection extends Collection<Product>{ sumPrices(): number; } // 限制泛型型別為Product | Employee interface PersonCollection<T extends Product | Employee> extends Collection<T>{ getNames(): string[]; } ``` #### 實作一個泛型介面-沿用泛型型別 ```ts= type shapType = { name: string }; interface Collection<T extends shapType>{ add(...newItems: T[]): void; get(name: string): T; count: number; } // 類別使用和介面完全一樣的泛型參數 // ArrayCollection<T>類別使用implements宣告了Collection實作介面 // 而這個介面有泛型參數(必須符合shapeType型別{name: string} // ArrayCollection類別也沿用同樣的泛型型別 class ArrayCollection<T extends shapType> implements Collection<T>{ private items: T[] = []; add(...newItems: T[]): void { this.items.push(...newItems); } get(name: string): T { return this.items.find(i => i.name === name); } get count(): number { return this.items.length; } } let peopleCollection: Collection<Person> = new ArrayCollection<Person>(); peopleCollection.add(new Person("John", "Paris"), new Person("Jane", "London")); console.log(`Collection size: ${peopleCollection.count}`); ``` #### 實作一個泛型介面-限制或鎖定泛型參數 ```ts= class PersonCollection implements Collection<Person>{ private items: Person[] = []; add(...newItems: Person[]): void { this.items.push(...newItems); } get(name: string): Person { return this.items.find(i => i.name === name); } get count(): number { return this.items.length; } } let peopleCollection: Collection<Person> = new PersonCollection(); peopleCollection.add(new Person("John", "Paris"), new Person("Jane", "London")); console.log(`Collection size: ${peopleCollection.count}`); ``` #### 實作一個泛型介面-用一個抽象類別實作泛型介面 - ArrayCollection<T>是一個泛型抽象類別,實作了Collection<T>介面部分內容 子類別ProductCollection與PersonCollection各自實作了get()方法 ```ts= type shapType = { name: string }; interface Collection<T extends shapType>{ add(...newItems: T[]): void; get(name: string): T; count: number; } abstract class ArrayCollection<T extends shapType> implements Collection<T>{ protected items: T[] = []; add(...newItems: T[]): void { this.items.push(...newItems); } abstract get(name: string): T; get count(): number { return this.items.length; } } class ProductCollection extends ArrayCollection<Product>{ get(searchTerm: string): Product { return this.items.find(i => i.name === searchTerm); } } class PersonCollection extends ArrayCollection<Person>{ get(searchTerm: string): Person { return this.items.find(i => i.name === searchTerm); } } let peopleCollection: Collection<Person> = new PersonCollection(); peopleCollection.add(new Person("John", "Paris"), new Person("Jane", "London")); let productsCollection: Collection<Product> = new ProductCollection(); productsCollection.add(new Product("Phone", 699), new Product("Tablet", 899)); [peopleCollection, productsCollection].forEach(c => console.log(`Collection size: ${c.count}`)); ```