# TYPESCRIPT
**PREVIOUS**
> [TYPESCRIPT](/RSUCzVz_QkaIY2-TxwQrKA)
> [name=Layors] from 2022/10/17
>
## Generic
TypeScript才有的類型,JavaScript沒有
generic是一個type,連結到了另一個type的type
generic的用處在於他告訴了TypeScript這個type最後會產出什麼type,進而可以用確定過的型別做接下來的操作
這個type有著很大的彈性
### 內建的generic
- Array
Array是一種generic
```typescript=
//和string做連結的Array,裡面放的都是string
const names: Array<string> = [];
```
- promise
promise也是一種generic
```typescript=
//和string做連結的Promise,這個Promise會回傳string
const promise: Promise<string> = new Promise((resolve, reject) => {
resolve('done!')
});
```
### 建立generic type
```typescript=
function merge(obj1: object, obj2: object) {
return { ...obj1, ...obj2 };
}
let mergedObj = merge({ name: "1" }, { age: 30 });
下面這段code會報錯
console.log(mergedObj.name);
```
spread可以把兩個object合在一起,但我們無法去取得結合之後的object的property
因此才需要用到generic
```typescript=
function merge<T, U>(obj1: T, obj2: U) {
return { ...obj1, ...obj2 };
}
let mergedObj = merge({ name: "1" }, { age: 30 });
```
T跟U是通常會設定的變數名字,可以自己換
這樣做之後TypeScript會把mergedObj看成是T跟U的intersection,因此name跟age都可以取得了
### Constraints
```typescript=
let mergedObj = merge({ name: "1" }, 30);
```
上面這段程式碼我們只會得到一個{ name: "1" }的物件,30就這麼不見了
假設我們不小心輸入了不對的型別,這個時候TypeScript並不會偵測到錯誤,因此要設定限制
```typescript=
function merge<T extends object, U extends object>(obj1: T, obj2: U) {
return { ...obj1, ...obj2 };
}
```
用extends把它限制為object,這樣收到不是object形式的變數時就會報錯了
限制可以是自訂型別,也可以是union
### keyof
```typescript=
function extractAndConvert(obj: object, key: string) {
return obj[key];
}
```
這樣寫的話TS會報錯,因為他無法確認你的obj裡面到底會不會有key的這個property
```typescript=
function extractAndConvert<T extends object, U extends keyof T>(obj: T, key: U) {
return obj[key];
}
```
而這樣寫的話就是跟TS說U是T的key,因此不會報錯,但如果key是object裡沒有東西的話一樣會報錯
### generic class
```typescript=
class DataStorage<T> {
private data: T[] = [];
addItem(item: T) {
this.data.push(item);
}
removeItem(item: T) {
this.data.splice(this.data.indexOf(item), 1);
}
getItems() {
return [...this.data];
}
}
const textStorage = new DataStorage<string>();
const numberStorage = new DataStorage<number>();
```
generic一樣可以用在class裡
可以用在同一個結構想要重複用在不同type的時候
像textStorage用來存string,numberStorage用來存number
而object在這裡會有點問題
```typescript=
const objStorage = new DataStorage<object>();
objStorage.addItem({ name: "me" });
objStorage.addItem({ name: "you" });
objStorage.removeItem({ name: "me" });
//留下來的會是{ name: "me" }
```
因為在removeItem裡面的那個object雖然內容是完全一樣的,但這樣在JavaScript裡其實等同於建立了一個新object,跟存進去的那個object已經是不一樣的東西了
解決方法:把他assign到一個變數裡
```typescript=
removeItem(item: T) {
if (this.data.indexOf(item) === -1) {
return;
}
this.data.splice(this.data.indexOf(item), 1);
}
...
const obj = { name: "me" };
objStorage.addItem(obj);
objStorage.addItem({ name: "you" });
objStorage.removeItem(obj);
//留下來的就是{ name: "you" }了
```
### generic utility type
- Partial
```typescript=
interface CourseGoal {
title: string;
description: string;
date: Date;
}
function createCourseGoal(
title: string,
description: string,
date: Date
): CourseGoal {
let courseGoal: CourseGoal = {};
courseGoal.title = title;
courseGoal.description = description;
courseGoal.date = date;
return courseGoal;
}
```
這是JavaScript的作法,但在TypeScript不會通過,因為courseGoal在建立時是空的,沒有interface裡要求他一定要有的屬性
而Partial的功用在於,可以讓interface或object type裡的property全部暫時變成optional
```typescript=
function createCourseGoal(
title: string,
description: string,
date: Date
): CourseGoal {
let courseGoal: Partial<CourseGoal> = {};
courseGoal.title = title;
courseGoal.description = description;
courseGoal.date = date;
return courseGoal as CourseGoal;
}
```
最後我們不能return partial,所以要加as讓它通過
- readonly
不可對Object內容重新指派
### generic vs union
union type接受了各種type
`a: string | number | boolean`
generic的T也是可以接受各種type,但一旦T的type被定義完便不能再更改
```typescript=
class Gene<T extends string | number | boolean>{};
//Gene可以接受string,number,boolean,但在這裡選擇了string以後,T就定型為string了,不能放進number或boolean
const G = new Gene<string>();
```
## Decorator
除非你要自己寫框架,不然通常不會自己寫decorator
了解如何使用它並了解他的方便性
decorator讓你可以在class被設置好之後再做額外的設定
要先去tsconfig.json做設定,把experimentalDecorators設為true
```json=
"experimentalDecorators": true,
```
decorator function的名字通常第一個字會是大寫
### class decorator
使用方法:
```typescript=
function Logger(target: Function) {
console.log("Decorating...");
}
@Logger
class Person {
name = "L";
constructor() {
console.log("Creating...");
}
}
hello() {
console.log("Hello!");
}
const L = new Person();
```
先寫好decorator之後,再放到你想要用的class上面,前面要加@
當你的class被define之後,decorator會馬上執行
所以console會先顯示"Decorating..."再顯示"Creating..."
這裡decorator的target拿到的會是constructor

### decorator factory
```typescript=
function Logger(message: string) {
console.log("logger");
return function (target: Function) {
console.log(message);
};
}
function test(message: string) {
console.log("tester");
return function (c: Function) {
console.log(message);
};
}
@Logger("create person")
class Person {
name = "L";
constructor() {
console.log("Creating...");
}
}
```
改成這種形式,便可傳值進decorator function裡
一個class可以使用多個decorator
```typescript=
@test("testing")
@Logger("create person")
class Person {
...
}
const L = new Person();
```
decorator執行順序由下而上,所以會先看到"create person"再看到"testing"
但這只局限於在return function裡的內容
外部的內容一樣是由上而下
所以會先看到"tester"再看到"logger"
> [name=Layors] from 2022/10/18
### property decorator
```typescript=
function log(target: any, propertyName: string) {
console.log(target, propertyName);
}
class Product {
@log
title: string;
_price: number;
constructor(t: string, p: number) {
this.title = t;
this._price = p;
}
}
```

property decorator需要兩個參數
其中target會抓到該class的prototype,propertyName會抓到你選的property
### accessor decorator
accessor: getter, setter
要有三個參數
```typescript=
function log2(target: any, name: string, descriptor: PropertyDescriptor) {
console.log(target);
console.log(name);
console.log(descriptor);
}
...
@log2
set price(val: number) {
if (val >= 0) {
this._price = val;
}
}
```

target為class的prototype
name為accessor的名字
descriptor可以看到accessor的狀態,上圖可以看到setter有定義;getter是undefined
### method decorator
要有三個參數
```typescript=
function log3(target: any, name: string, descriptor: PropertyDescriptor) {
console.log(target);
console.log(name);
console.log(descriptor);
}
...
@log3
getPriceWithTax(tax: number) {
return this._price * (1 + tax);
}
```
基本上跟accessor decorator差不多
target是什麼要看method是什麼類型
如果是static,那會拿到constructor
不是的話則拿到class prototype
name為accessor的名字
descriptor可以看到method的狀態
### parameter decorator
```typescript=
function log4(target: any, name: string, position: number) {
console.log(target);
console.log(name);
console.log(position);
}
...
getPriceWithTax(@log4 tax: number) {
return this._price * (1 + tax);
}
```
target一樣看method的類型決定取得的東西
name這裡是指這個參數在的method的名字
position是指這個參數在哪個位置,第一個參數是0,第二個參數是1,以此類推