###### tags: `Typescript` # Typescript tutorial [youtube教學](https://www.youtube.com/watch?v=2pZmKW9-I_k&list=PL4cUxeGkcC9gUgr39Q_yD6v-bSyMwKPUI) ## Compiling TypeScript - `tsc filename.ts`: 將ts檔 Compiling to TypeScript, 每次修改完code都要執行一次才會重新 Compiling - `tsc src/filename.ts`: 如果檔案不在目前terminal所在路徑, 可以再加src路徑, 作用同上 - `tsc filename.ts -w`: 修改完code 會即時將ts檔 Compiling to TypeScript, 可console看即時更新結果 ## Type Basics 2~6 ```typescript= //string, number, boolean let name:string; let age: number; let isOpen: boolean; //array let arr1: string[] = []; //array內value只能是string let arr2: number[] = []; //array內value只能是number let arr3: any[] = []; //array內value可以是任意型別 //union types let mixed: (string|number|boolean)[] = [] let uuid: string|number; uuid = 'emma'; uuid = 123; //object //任意物件內容都可以 let obj: object; obj = {name:'emma', age:123}; //限定物件key與value型別 let objDetail:{ name:string, age: number, isDrive: boolean, }; objDetail = { name: 'emma', age: 123, isDrive: false, }; // 物件內value型別可以任意 let obj = {name: any, age: any}; obj = { name: 'emma', age: 123 }; // 任意型別 let age: any = 25; age = true; ``` ## Better Workflow & tsconfig 7, Modules 14 - terminal cd 到 src file - `tsc --init`在src資料夾會出現 tscconfig.json ```javascript= // tscconfig.json { "compilerOption": { "target": "es6", //指定編譯生成的JS版本 "module": "commonjs", //指定生成哪種模組 ex:es2015 "rootDir": "./src" //.ts檔案所在位置 "outDir": "./public": // .ts要compiler放到哪裡 "forceConsistentCasingInFileNames": true, //強制區分大小寫, 默認為false "files": ["hello.ts"], //屬性指定要編譯的 TS 檔案 "include": ["src"]或["src/**/*.ts"], //屬性設定編譯時包含哪些檔案或資料夾, 路徑可調整 "exclude": ["node_modules"], //屬性設定編譯時排除哪些檔案或資料夾 }, } // *-表示匹配0至多個字元(不包含分隔符號) // ?-匹配一個相符字元(不包含分隔符號) // **/-表示匹配所有子資料夾 //ps:以下code可以過, 但如果forceConsistentCasingInFileNames為true時, 會報錯誤:'2a' !== '2A' //2a.ts export const PI = 3.1415926; //1a.ts import PI from './2A.ts'; function fun(){ return PI; } ``` - `tsc -w` 在vscode的filename.ts檔案, 下這個指令`tsc -w` 開啟同步compiler, 當改code的時候, 就會同步輸出js檔, 只要一更改ts就會馬上改js [tscconfig.json設定參考](https://github.com/hstarorg/HstarDoc/blob/master/%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/TypeScript%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6tsconfig%E7%AE%80%E6%9E%90.md) ## Function Basics 8 ```typescript= //宣告型別為 Function let greet: Function; greet = () => { console.log('hello world'); } //宣告function的參數型別 //有參數c時宣告型別, 並且有 defaultValue 10 const add = (a:number, b:number, c:number | string = 10) => { console.log(a+b); console.log(c); //如果沒有= defaultValue 10, log結果為 undefind } add(5,10, 'emma'); //有參數c時宣告型別, 且c不能=預設 defaultValue 10 const add = (a:number, b:number, c?:number | string) => { console.log(a+b); console.log(c); //如果沒有= defaultValue 10, log結果為 undefind } add(5,10, 'emma'); //定義function沒有回傳value: void const plusFn = (a:number, b: number, c?:number):void =>{ console.log(a+b); console.log(c); } const plusFn = (a:number, b: number, c?:number):number =>{ return a+b+c; } ``` ## Type Aliases 9 ```typescript= //用type宣告型別 type StringOrNum = string|number; type ObjWithName = {name:string, uid:StringOrNum}; const loginFn = (uid:StringOrNum, name:string) => { console.log(`${name} has a uid is ${uid}`); } const helloFn = (user:ObjWithName) => { console.log(`${user.name} say hello!`); } ``` ## Function Signatures 10, 宣告function三種例子, 參數與回傳值型別的差異 - 宣告function型別: ```typescript= let greet: Function; //example1: 宣告greet變數有兩個參數, 並定義型別, 與回傳值型別 let greet: (a:string, b:string) => void; greet = (name:string, greeting:string):void => { console.log(`${name} says ${greeting}`); } // example2: 如果有宣告回傳值則邏輯結束一定要回傳符合型別的value let cal: (a:number, b:number, action:string) => number; //回傳number cal= (a:number, b:number, action:string) => { //if 沒有else會報錯, 一定要回傳number if(action === 'add') { return a+b }else { return a-b } } // example3: 參數是物件 let loginFn: (obj: {name: string, age:number}) => void; type UserType = {name: string, age:number}; loginFn = (user:UserType) => { console.log(`${user.name} is ${user.age} years old.`); } ``` ## The DOM & Type Casting 11: 宣告DOM的型別有哪些 ```typescript= //example1: HTMLFormElement or HTMLAllCollection....等會有提示, 如下: //<form> id="type" const form = document.querySelect('.main') as HTMLFormElement; console.log(form.children); //example2: //<select> id="type" const type = document.querySelect('#type') as HTMLSelectElement; //<input> id="type" const type = document.querySelect('#type') as HTMLInputElement; //<form> id="type" const type = document.querySelect('#type') as HTMLFormElement; ``` ## Classes 12, Public, Private & Readonly 13 ```typescript= class Invoice { readonly client:string; //不管class內外都只能讀, 不能重新assign value private detail:string; //只能在class裡面access和賦值 public amount:number; //class外也可以access和賦值 constructor(c: string, d: string, a:number){ this.client = c; this.detail = d; this.amount = a; } //另一種寫法 constructor( readonly client:string; private detail:string; public amount:number; ){} format(){ // this.client = 'something else' // error, 因為client是readonly不能重新賦值 return `${this.client} owes ${this.detail} for ${this.amount}` } } const invOne = new Invoice('mario', 'work on the mario website', 250); const invTwo = new Invoice('luigi', 'work on the mario website', 300); //class就是一種型別 let invoices: Invoice[] = []; //class定義型別 invoices.push(invOne); invoices.push(invTwo); invoices.forEach(inv =>{ //因為inv.detail是private所以不能在class外存取, 但可從inv.format()取到detail的值, 因為format()是在class裡面access detail console.log(inv.client, inv.detail, inv.amount, inv.format()); }) ``` ## Interfaces 15 ```typescript= //interface定義型別, 名稱要大寫開頭 interface User { name: string; age: number; speak(a: string): void; spend(b: number): number; } // 宣告userA的型別為User const userA: User = { name: 'emma', age: 123, speak(text: string):void { console.log(text); }, spend(amount: number): number { console.log(amount); return amount; } } //用Interfaces宣告function argument 型別範例 const userFn = (users:User) => { console.log('hello', person.name); } userFn(userA); //執行userFn ``` ## Interfaces with Classes 16: class如何使用Interfaces ```typescript= // src/interfaces/HasFormatter.ts // 定義format回傳字串 export interfaces HasFormatter { format(): string; } // src/classes/Invoice.ts import { HasFormatter } from '../interfaces/HasFormatter.ts'; export class Invoice implements HasFormatter { constructor( readonly client:string; //不管class內外都只能讀, 不能重新assign value private detail:string; //只能在class裡面access和賦值 public amount:number; //class外也可以access和賦值 ){} format(){ // this.client = 'something else' // error, 因為client是readonly不能重新賦值 return `${this.client} owes ${this.detail} for ${this.amount}` } } ``` ## Rendering an HTML Template 17 - 參照react寫法即可 ## Generics(泛型) 18 - Generics(泛型)很重要的一點,就是讓我們寫的方法可以適用在不同的型別,而非只能使用在單一型別。 - 定義泛型 ```typescript= // type type Dict<T> = { value: T; }; // interface interface WrappedValue<T> { value: T; } // arrow function const identity = <T>(x: T): T => x; // function function identity<T>(x: T): T { return x; } ``` - 範例如下:可以dynamic defind data type也可以重複使用uid, name, errorCode ```typescript= interface Resource<T> { data: T; //can dynamic defind uid: number; //repeact use name: string; //repeact use errorCode: number; //repeact use } const docObj:Resource<object> = { data: {name: 'emma'}; uid: number; name: string; errorCode: number; } const docStrArr: Resource<string[]> ={ data: ['emma', 'mark', 'lisa']; uid: number; name: string; errorCode: number; } ``` ![](https://i.imgur.com/CRTqzXA.png) [Generic範例: type可以重複使用就看傳入的type是什麼1](https://github.com/total-typescript/typescript-generics-workshop/blob/main/src/01-dry-interfaces.solution.1.ts) [Generic範例: type可以重複使用就看傳入的type是什麼2](https://github.com/total-typescript/typescript-generics-workshop/blob/main/src/01-dry-interfaces.problem.ts) ## 帶有限制的泛型 Type Parameter with Constraints:透過 `extends` 的使用,可以建立帶有**「限制」**的泛型: ```typescript= interface WrappedValue<T extends string> { value: T; } // ⭕️ T 滿足 string 的型別 const val: WrappedValue<'Aaron' | 'PJ'> = { value: 'Aaron', }; // ❌ T 不滿足 string 時會噴錯 // Type 'number' does not satisfy the constraint 'string'. const val: WrappedValue<number> = { value: 30, }; // ❌ 因為沒有給 T 預設值,所以不能留空 // Generic type 'WrappedValue<T>' requires 1 type argument(s). const val: WrappedValue = { value: 30, }; ``` - 實際的例子像這樣: ```typescript= // 接收 array: T[] 做為參數,回傳 Dictionary {[k: string]: T} // 若把 T 的限制 { id: number } 拿掉的話,將會沒辦法確定 T 是帶有 id 的物件 const arrayToDict = <T extends { id: number }>(array: T[]): { [k: string]: T } => { const out: { [k: string]: T } = {}; array.forEach((val) => { out[val.id] = val; }); return out; }; ``` ## 帶有預設值的泛型 Generic parameter defaults - 可以給泛型預設值,舉例來說,下面的程式碼指的是:沒有給 T 的話,T 預設的型別會是 string ```typescript= interface WrappedValue<T = string> { value: T; } ``` - 但如果只是定義預設值的話,使用者可以任意更改該型別,例如: ```typescript= // 預設 WrappedValue 中使用的型別是 string,但使用者可以改成自己想要使用的 const val: WrappedValue<number> = { value: 30, }; ``` - 這時候,可以透過 extends 限制使用者給定的泛型,例如: - 如果沒有提供 T 則預設該型別是 string - 如果有給 T - T 需要滿足 string,否則會噴錯 - 當 T 滿足 string 時,T 會變成變成實際代表的型別 ```javascript= interface WrappedValue<T extends string = string> { value: T; } // 如果沒有提供 `T` 則預設該型別是 `string` const val: WrappedValue = { value: 'hello', }; // ❌ 如果有給 T,但 T 不滿足 string 時,會噴錯 // Type 'number' does not satisfy the constraint 'string'. const val: WrappedValue<number> = { value: 30, }; // ⭕️ 如果有給 T,當 T 滿足 string 時,T 會變成變成實際代表的型別 const val: WrappedValue<'Aaron' | 'PJ'> = { value: 'Aaron', }; ``` [pjchender: generic進階用法](https://pjchender.dev/typescript/ts-generics/) ## Enums 19 - 基本跟在function component外宣告const物件類似 - 差異是 enums 是常數希望不能被更改, 提高程式可讀性 - 範例: value可以是數字or字串 ```typescript= enum requestStatusCodes { error = 400, success = 200, } enum requestWrongCodes { missingParameter = 'A', wrongParameterType = 'B', invalidToken = 'C', } ``` [TypeScript | 善用 Enum 提高程式的可讀性 - 基本用法 feat. JavaScript](https://medium.com/enjoy-life-enjoy-coding/typescript-%E5%96%84%E7%94%A8-enum-%E6%8F%90%E9%AB%98%E7%A8%8B%E5%BC%8F%E7%9A%84%E5%8F%AF%E8%AE%80%E6%80%A7-%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95-feat-javascript-b20d6bbbfe00) ## Tuples 20 ```typescript= let values: [string, string, number]; // defind type values = [obj.name, obj.address, obj.age]; //declare value newValues = [...]; // 解壓縮前要defind type才不會報錯 ``` ## 斷言 as unknown - 通常api拿回來的資料ts無法事先知道, 因此可以用as斷言人工定義會來的資料會是什麼類型 ```javascript= type Data ={ userId: number, id: number, title: string, completed: boolean, } async function getData() { const res = await fetch('https://jsonplaceholder.typicode.com/todo/1'); const data = await res.json() as Data; } ``` ## Record<key, value> - 有時候只知道key跟value的型別 ```javascript= interface CatInfo { age: number; breed: string; } type CatName = "miffy" | "boris" | "mordred"; const cats: Record<CatName, CatInfo> = { miffy: { age: 10, breed: "Persian" }, boris: { age: 5, breed: "Maine Coon" }, mordred: { age: 16, breed: "British Shorthair" }, }; ``` ## Pick<Type, Keys> - 只需要某個type裡面某幾個項目, **篩選出某幾項** ```javascript= interface Todo { title: string; description: string; completed: boolean; } type TodoPreview = Pick<Todo, "title" | "completed">; const todo: TodoPreview = { title: "Clean room", completed: false, }; ``` ## Omit<Type, Keys> - 省略掉某個type的某幾項, **省略掉某幾項** ```javascript= interface Todo { title: string; description: string; completed: boolean; createdAt: number; } type TodoPreview = Omit<Todo, "description">; const todo: TodoPreview = { title: "Clean room", completed: false, createdAt: 1615544252770, }; type TodoInfo = Omit<Todo, "completed" | "createdAt">; const todoInfo: TodoInfo = { title: "Pick up kids", description: "Kindergarten closes at 5pm", }; ``` ## react component參數寫法 ```javascript= type UsersDataType = { data: { total: number; users: UserType[]; factories: GroupType[]; departments: GroupType[]; groups: GroupType[]; }; } type PropType = { data: UsersDataType; updateTable: Function; }; //用在function component的參數方法 const App: React.FC<PropType> = ({data, updateTable}) => { return<div>首頁</div> } ``` ## Wrap Up 21略