# 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,以此類推