# 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}`));
```