# 🏅 Day 12- 泛型約束 extends
泛型約束允許我們為型別參數 `T` 定義一個約束條件。這樣做的目的是為了確保泛型不僅能代表任何型別,還能滿足特定的接口或擁有某些屬性。
會使用 `extends` 關鍵字來實做出這種約束,下方我們來分享範例程式碼
## 範例一:確保泛型是數字
在這個範例中,我們創建一個函式來翻倍一個數字。我們將使用泛型約束來確保傳入的參數是一個數字。
```tsx=
function doubleValue<T extends number>(value: T){
return value * 2;
}
// 正常使用
const doubleNum = doubleValue(10); // 回傳 20
// 錯誤使用(將導致編譯錯誤,因為 'Hello' 不是數字)
// const notANumber = doubleValue("Hello");
```
原本可以放任意型別的`T`,多加上了`extends` 為 `<T extends number>`
這句話用白話文來講就像是:「**將 `T` 型別參數設了一個門禁系統(extends),只有數字才能進去。**」
以致於後面函式代入 `srting` 字串時會出錯。

## 範例二: 確保代入型別擁有對應屬性
在前一個章節,你會發現到型別參數 `<T>` 可以代入各種型別,看似好用,但會有個潛在問題如下:
```tsx=
function returnWithLength<T>(arg: T): T {
console.log(arg.length); // 錯誤:型別 'T' 沒有 length 屬性
return arg;
}
// 執行函式
returnWithLength([1, 2, 3]);
// 這裡會產生錯誤
// 因為 TypeScript 不知道 'T' 是否含有 'length' 屬性
```
你可以將上面程式碼張貼到自己的編輯器,在這個範例中,函式 **`returnWithLength`** 嘗試讀取泛型 **`T`** 的 **`length`** 屬性。
這是因為 TypeScript 編譯器無法確定傳入的參數是否包含 **`length`** 屬性,以致於導致編譯錯誤。
這時候泛型约束(Generic Constraints)就派上用場了:
```tsx=
function returnWithLength<T extends { length: number }>(arg: T): T {
console.log(arg.length); // 正確:現在 TypeScript 知道 'T' 包含 'length' 屬性
return arg;
}
returnWithLength([1, 2, 3]); // 正確:陣列有 'length' 屬性
returnWithLength("Hello World"); // 正確:字串也有 'length' 屬性
// 以下將導致編譯錯誤,因為數字不包含 'length' 屬性
// returnWithLength(123);
```
**`{ length: number }`** 就是確保傳入的值,能如何該屬性與對應的型別
像是陣列有 `length` 屬性,型別是 `number`
```javascript=
console.log([1,2,3].length)
// 陣列有 length 屬性,所以會印出 3,代表陣列長度為 3
```
字串也有 `length` 屬性,型別也是 `number`
```javascript=
console.log("hello".length);
// 字串也有 length 屬性,印出 5,表示字串長度為 5
```
但數字就會出錯了,因為數字型別裡沒有屬性 `length`;
```javascript=
console.log(12345.length)
// 印出 Uncaught SyntaxError: Invalid or unexpected token
```
這種約束在處理陣列、字串或任何帶有 **`length`** 屬性的物件時特別有用,因為它使函式能夠安全地存取 **`length`** 屬性,同時又保持泛型的靈活性。
## 範例三:確保物件擁有特定屬性或方法
當希望傳入的物件不僅僅是某個型別,而且還必須擁有特定的屬性或方法時,泛型約束非常有用。這在建立通用函式庫蠻實用的。
例如你正在建立一個函式,該函式需要處理各種不同的資料物件,但**每個物件都必須有一個共同的 **`id`** 屬性時**。泛型約束就能派上用場。
```tsx
interface Identifiable {
id: string;
}
function processItem<T extends Identifiable>(item: T) {
console.log(`處理物件,ID 為: ${item.id}`);
// ... 其他邏輯
}
// 會正常運作,因為物件符合 Identifiable 介面
processItem({ id: '123', name: '項目1' });
// 會編譯錯誤,因為缺少 id 屬性
processItem({ name: '項目2' });
```
在這個例子中,任何擁有 **`id`** 屬性的物件都可以被 **`processItem`** 函式接受,在 TypeScript 中,**只要物件滿足了介面的最小要求,它就被認為是符合該介面的**,即使它有額外的屬性也沒問題。
這樣設計方式,就能開發一個既靈活又嚴謹的方法,藉此處理各種不同的資料結構。
## 開發題
### **題目一:計算陣列總和**
開發一個函式 **`calculateSum`**,並接受一個數字陣列作為參數,來計算出其總和。請使用泛型約束來確保傳入的參數必須是數字陣列。
```tsx=
// 請改寫為泛型函式,並使用泛型約束
function calculateSum{
return ;
}
// 正常使用
const total = calculateSum([1, 2, 3, 4]); // 回傳 10
// 錯誤使用(會編譯錯誤,因為參數不是數字陣列)
// const errorTotal = calculateSum(["a", "b", "c"]);
```
### **題目二: 篩選擁有特定屬性的物件陣列**
開發一個函式 **`filterItems`**,該函式接受一個物件陣列,並回傳所有具有特定屬性的物件。使用泛型約束來確保每個物件至少包含該屬性。
```tsx=
interface WithName {
name: string;
}
// 請調整為泛型函式,並使用 WithName 做為泛型約束條件,確保每個物件至少包含該屬性。
function filterItems(){
return items.filter(item => item.name.startsWith("特定條件"));
}
// 正常使用
const filteredItems = filterItems([{ name: '特定條件物件1' }, { name: '其他物件' }]);
// 錯誤使用(將導致編譯錯誤,因為陣列中的物件沒有 name 屬性)
// const errorFilteredItems = filterItems([{ id: 1 }, { id: 2 }]);
```
### **題目三: 泛型介面中的多屬性檢查**
開發一個函式 **`processData`**,該函式接受一個具有 **`id`** 和 **`data`** 屬性的物件。使用泛型約束來確保傳入的物件符合此結構。
```tsx=
interface DataItem {
id: string;
data: any;
}
// 請改寫為泛型函式,並使用泛型約束
function processData() {
console.log(`處理資料,ID 為: ${item.id}, Data: ${item.data}`);
// ... 其他邏輯
}
// 正常使用
processData({ id: '123', data: '任何資料' });
// 錯誤使用(將導致編譯錯誤,因為物件不符合 DataItem 結構)
// processData({ id: '123' });
```
### **題目四: 事件處理器**
開發一個函式 **`handleEvent`**,該函式接受一個事件名稱和一個處理函式。使用泛型約束來確保事件名稱是特定集合的一部分。
```tsx=
type EventName = "click" | "mouseover";
// 請改寫為泛型函式,並使用泛型約束
function handleEvent(eventName, handler) {
// ... 註冊事件處理函式邏輯,這裡僅示意不用寫
}
// 正常使用
handleEvent("click", () => console.log("Clicked!"));
// 錯誤使用(將導致編譯錯誤,因為 'scroll' 不是 EventName 的一部分)
// handleEvent("scroll", () => console.log("Scrolled!"));
```
### **題目五: 泛型約束用於資料處理的函式**
開發一個函式 **`processData`**,該函式接受一個包含 **`data`** 和 **`timestamp`** 屬性的物件。使用泛型約束來確保物件包含這些屬性。
```tsx
interface DataWithTimestamp {
data: any;
timestamp: Date;
}
// 請改寫為泛型函式,並使用泛型約束
function processData(item) {
console.log(`資料: ${item.data}, 時間戳: ${item.timestamp}`);
}
// 正常使用
processData({ data: "some data", timestamp: new Date() });
// 錯誤使用(將導致編譯錯誤,因為缺少 timestamp 屬性)
// processData({ data: "some data" });
```
## 回報流程
將答案寫在 CodePen,並貼至底下回報就算完成了喔!
解答位置請參考下圖(需打開程式碼的部分觀看)

<!-- 解答:
-->
回報區
---
| Discord | CodePen / 答案 |
|:-------------:|:----------------------------------------------------------------:|
|洧杰|[Codepen](https://codepen.io/hexschool/pen/poYgYqW?editors=1010)|
|展誠|[CodePen](https://codepen.io/hedgehogkucc/pen/yLwoJNM?editors=1010)|
|連小艾|[CodePen](https://codepen.io/bolaslien/pen/PoLKzjq?editors=0012)|
|苡安|[CodePen](https://codepen.io/yi-an-yang/pen/poYrbjR)|
|clairechang|[Notion](https://claire-chang.notion.site/Day-12-extends-fcd66187dfb64ee1a9f2aac2e1acdfcc)|
|LinaChen|[CodePen](https://codepen.io/LinaChen/pen/ZEPJpXP)|
|hiYifang|[HackMD](https://hackmd.io/@gPeowpvtQX2Om6AmD-s3xw/r1dPCijta)|
|Mi|[CodePen](https://codepen.io/Mi-Jou-Hsieh/pen/vYPJXqe?editors=1011)|
|hannahpun|[CodePen](https://codepen.io/hannahpun/pen/VwRzPdo?editors=0011)|
|HsienLu|[CodePen](https://codepen.io/Hsienlu/pen/NWJvpXM?editors=1011)|
|JC|[CodePen](https://codepen.io/jcsamoyed/pen/gOExmeG?editors=0012)|
|yunhung|[CodePen](https://codepen.io/ahung888/pen/zYbdwVE?editors=0011)|
|翰毅|[CodePen](https://codepen.io/yzuigtdw-the-animator/pen/yLwoXVb?editors=1111)|
|hannahTW|[CodePen](https://codepen.io/hangineer/pen/qBvXPPX?editors=1011)|
|Bryan Chu|[CodePen](https://codepen.io/bryanchu10/pen/PoLKEdO)|
|wendy_.li|[HACKMD](https://hackmd.io/PcmFgqZwRd-4Ep3-LgK5_Q#20240123-%E4%BA%8C--%E6%B3%9B%E5%9E%8B%E7%B4%84%E6%9D%9F-extends)
|erwin阿瀚|[CodePen](https://codepen.io/yohey03518/pen/bGZrLrZ?editors=1010)
|deedee1215|[CodePen](https://codepen.io/diddy032/pen/zYbdRjN)
|Teddy|[CodePen](https://codepen.io/TaideLi/pen/jOJLzrM)
|jasperlu005|[Codepen](https://codepen.io/uzzakuyr-the-reactor/pen/zYbdWEj?editors=0011)|
|精靈|[CodePen](https://codepen.io/justafairy/pen/yLworaE)|
|Starr|[CodePen](https://codepen.io/StarrZhong/pen/LYajKzg)|
|BonnieChan|[CodePen](https://codepen.io/Bonnie-chan-the-bold/pen/PoLKMzb?editors=0012)|
|Alyce|[CodePen](https://codepen.io/alycehwy/pen/wvOqJWr)|
|皓皓|[HackMD](https://hackmd.io/@cutecat8110/ByDPSI0t6)|
|Lisa|[CodePen](https://codepen.io/lisaha/pen/jOJGWYx)|
|Otis|[CodePen](https://codepen.io/humming74/pen/rNRGLYY?editors=1012)|
|77_0411|[CodePen](https://codepen.io/chung-chi/pen/LYazzMz?editors=0011)|
|YC|[HackMD](https://hackmd.io/SKoJd3EsTlitnjzCx4Rarg?view)|
|Kai|[CodePen](https://codepen.io/kaiyuncheng-the-styleful/pen/dyrVZEL?editors=0011)|
|Amberhh | [codepen](https://codepen.io/Amberhh/pen/bGZrPyg?editors=0011) |
|shan13 | [codepen](https://codepen.io/yishan13-tsai/pen/mdoBvJw) |
|銀光菇 | [codepen](https://codepen.io/genesynthesis/pen/ZEPaWLN) |
|神奇海螺| [codepen](https://codepen.io/ksz54213/pen/bGZYQKb)|
|rikku1756| [codepen](https://codepen.io/rikkubook/pen/XWGzKRR?editors=1011)|
|Snorlax|[HackMD](https://hackmd.io/@snorlaxpock/BygEiQYca)|
|薏慈|[Codepen](https://codepen.io/its_wang/pen/ZEPoZXb)|
|leave3310|[Codepen](https://codepen.io/leave3310-the-looper/pen/mdoGOZo?editors=0011)|
|我是泇吟|[CodePen](https://codepen.io/kljuqbxs/pen/Rwzmqra)|