---
# System prepended metadata

title: TypeScript使用泛型

---

# 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=
// 不需要設定Ｕ
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}`));
```