---
# System prepended metadata

title: TYPESCRIPT

---

# 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
![](https://i.imgur.com/1Nl1wcb.png)


### 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;
  }
}
```
![](https://i.imgur.com/kJiVtcw.png)
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;
    }
  }
```
![](https://i.imgur.com/muZq2bC.png)
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，以此類推
