<style> .tilte-bg { background: #C25F3C; color:#FFF; padding:2px 8px 5px 8px; border-radius:5px; font-size:13px; display:inline-block; } .warning{ color:#A60101; } .tip { color:#A60101; } p{ font-size:15px; } </style> # 讀書會 - 02 - TypeScript ###### tags: `讀書會` > 2021/02/13 筆記 ### 關於型別 <p>「型別」是一組值並可以使用它們來執行特定的行為。</p> #### **1.JavaScript 中有 7 種原始型別 :** * Number * String * Boolean * Null * Undefined * Symbol * Object(包含 Function 和 Array) #### **2.TypeScript 基礎型別 :** * Array * Any * Enum * Void * Never * 另外包含 JavaScript 中有 7 種原始型別 --- ### 關於TypeScript 基礎型別 <h5 class="tilte-bg">any</h5> <p>允許賦值為任意類型 ( string . Array . Boolean...皆可 ),any 允許可以訪問任何操作、任意屬性(運算、函數),由於開發時期 any 型別 TypeScript 認為它可以是任何型別。所以調用不存在的方法 TS 也不會爆錯可以可以正常編譯,但 runtime 就會出錯(範例1)。<br/> any 返回的内容的類型都是 any 型別,即便為非相同型別相加也不會回報錯誤(範例2)。<br/></p> <p class="warning">※ 應盡可能避免使用它</p> ``` 範例1: let looselyTyped: any = 4; // OK, ifItExists might exist at runtime looselyTyped.ifItExists(); // OK, toFixed exists (but the compiler doesn't check) looselyTyped.toFixed(); ``` ``` 範例2: let a: any = 4; //any let b: any = ['apple']; //any let c = a + b //any ``` <h5 class="tilte-bg">unknown</h5> <p>可以指派任何型別的值給 unknown ,但要對 unknown 型別操作時,必須透過細分()轉成 unknown 以外的型別,否則便會出錯。<br/></p> <p class="warning">※ 應盡可能避免使用它</p> <h5 class="tilte-bg">number</h5> <p>number 在 TypeScript 包含了整數、浮點數、正數、負數以及 Infinity 或 NaN 都包含在裡面。處理很大的數字時可以使用數值分隔符號(底線)區隔,可以讓可讀性提高</p> ``` 範例1: let oneMillion = 1_000_000 // 等同 1000000 ``` <h5 class="tilte-bg">string</h5> <p>所有字串的集合。</p> <h5 class="tilte-bg">symbol</h5> <p>「型別」是一組值並可以使用它們來執行特定的行為。</p> <h5 class="tilte-bg">Enums</h5> <p>枚舉類型默認從0開始,如果想改從1開始將物件第一個key指向1範例如下。</p> ``` enum Status { SUCCESS = 1, // 1 PARAMETERERROR, // 2 ERROR, // 3 } 反查 console.log(`Status: ${Status[1]}, Number: ${Status.SUCCESS}`) // Status: SUCCESS, Number: 1 ---------------------------------------------------------------------------------------- enum Status { SUCCESS, // 0 PARAMETERERROR, // 1 ERROR, // 2 } function getServe(status:any){ switch (status) { case Status.SUCCESS: return 'status : Success'; case Status.PARAMETERERROR: return 'status : Parameter Error'; case Status.ERROR: return 'status : Error'; } } const result = getServe(1) console.log(result) // status : Parameter Error ``` <h5 class="tilte-bg">元祖</h5> <p>元祖是Array的子型別可以強制規定Array中每個所引上的值為乙支特定的型別。不同於其他型別,元祖宣告時必須明確的定型而 Typescript 會從[]中推稕列的型別</p> ``` const List :(string|number)[] = ['Dora',22,'Tom',28] // Duplicate identifier 'List' const ListSec :[string,number,string,number] = ['Dora',22,'Tom',28] // OK ``` <h5 class="tilte-bg">null</h5> <p>null 屬於特殊型別代表空值,null 型別唯一值為 null。</p> <h5 class="tilte-bg">undefined</h5> <p>undefined 屬於特殊型別代表值尚未被定義,undefined 型別唯一值為 undefined。</p> <h5 class="tilte-bg">void</h5> <p>void 屬於特殊型別,是沒有明確回傳任何東西的函式、沒有return述句的函式。</p> <h5 class="tilte-bg">never</h5> <p>never 屬於特殊型別,是永遠不會回傳任何東西的函式。</p> --- 靜態類型 Static Typing 定義類型後無法再修改,並繼承型別所有方法如下圖,靜態類型可以通過接口進行使用(範例1) ![](https://i.imgur.com/faf5bRF.png) ``` 範例1: interface Employee { name:string, age:number, department:string } let newbieA :Employee= { name:'Dora', age:18, department:'DNG' } console.log('newbieA :',newbieA) // newbieA :", { "name": "Dora", "age": 18, "department": "DNG" } ``` 靜態類型分為基礎靜態類型 / 對象靜態類型 類型註解:宣告變數+ 符號 ":" + 類型 類型推斷:即便宣告時沒有類型註解 Typescript 也會推斷分析目前值的類型;但如果 Typescript 無法分析變量類型就必須一定要寫類型註解 ``` let count:number = 100; ``` 基礎靜態類型 <h5 class="tilte-bg">number</h5> ``` let count:number = 100; ``` <h5 class="tilte-bg">string</h5> ``` let name:string = 'Tom'; ``` <h5 class="tilte-bg">nul</h5> <h5 class="tilte-bg">undefinde</h5> <h5 class="tilte-bg">boolean</h5> <h5 class="tilte-bg">void</h5> <h5 class="tilte-bg">symbol</h5> 對象靜態類型 <h5 class="tilte-bg">object類型</h5> 宣告物件Key對應value類型再賦值 ``` const Employee { name:string, age:number, department:string } = { name: "Dora", age: 18, department: "DNG" } ``` <h5 class="tilte-bg">array類型</h5> ``` const Employee :string [] = ['Tom','Dora','Kimy'] ``` <h5 class="tilte-bg">class類型</h5> ``` class Person{} const Man : Person = new Person() ``` <h5 class="tilte-bg">函數類型 </h5> ``` 當函式返回值必須是 string const getName : () => string = () => { return `Hello Anna` } ``` --- ### 複合型別 Union & Intersection #### **1.Union 聯集:** <p>使用 " | " 只要至少其中一個型別被判定滿足,不管其他型別有沒有完整補齊<p> ``` type UserOnlyInfo1 = { name:string, age:number } type UserOnlyInfo2 = { skill:string, workexperience:boolean } type UnionSet = UserOnlyInfo1 | UserOnlyInfo2 let correctInfo1 : UnionSet = { name:'Dora', age: 28, skill:'CSS3', workexperience:true } let correctInfo2 : UnionSet = { // name:'Rinatea', --> 雖然少了 UserOnlyInfo1 中 name 屬性,但是符合 UserOnlyInfo2 所有條件 age: 28, skill:'CSS3', workexperience:true } let wrongInfo1 : UnionSet = { name:'Jill', // age: 28, --> 少了 UserOnlyInfo1 中 age 屬性 skill:'Java', // workexperience:true --> 少了 UserOnlyInfo2 中 workexperience 屬性 } wrongInfo1 將出現以下錯誤訊息 Type '{ age: number; workexperience: true; }' is not assignable to type 'UnionSet'. ``` #### **2.Intersection 交集:** <p>使用 " & " 將兩組屬性進行結合,範例如下<p> ``` type UserInfo1 = { name:string, age:number } type UserInfo2 = { skill:string, workexperience:boolean } type IntersectionSet = UserInfo1 & UserInfo2 let correctInfo : IntersectionSet = { name:'Dora', age: 28, skill:'CSS3', workexperience:true } let wrongInfo1 : IntersectionSet = { // name:'Rinarea', --> 少了 name 屬性 age: 28, skill:'CSS3', workexperience:true } let wrongInfo2 : IntersectionSet = { name:'Jill', age: 28, skill:'Java', // workexperience:true --> 少了 workexperience 屬性 } ``` --- ### 函式 在 TypeScript 可定義函式參數之型別;如傳入型別不對的引數將提醒引數不可指定為參數定義型別(範例1) ``` 範例1: let add = ( a:number ,b:number ) => ( a + b ) console.log(add(10,30)) // 40 console.log(add(10,'a')) // Argument of type 'string' is not assignable to parameter of type 'number'. ``` 建構函式可建立一個新的 Function 物件,參數必須要是有效的 JavaScript 識別符號規則的字串,或是使用英文逗號「,」分隔開的字串清單(範例1)。 <p class="warning">※ 由於使用建構函式參數和回傳不具型別較不安全建議避免使用。</p> ``` 範例1: let greet = new Function('name','return "Hello " + name') console.log(greet('Tom')) // Hello Tom console.log(greet(['2','6'])) // Hello 2,6 ``` #### **1.選擇性參數:** 可以使用 "?" 將參數標示為選擇性是否為必要參數,擇性參數必須放在參數列的尾端(範例1)。 ``` 範例1: function messageLog( message:string,useId?:number ){ let time = new Date().toISOString() console.log(time , message , useId || 201401) } messageLog('User clicked on button',910501) // "2021-02-19T17:43:13.899Z", "User clicked on button", 910501 messageLog('User signed out') // "User signed out", 201401 (預設值) ------------------------------------------------------------------------------ 範例2: type Contest = { appId?:string useId?:number } function messageLog1( message:string,context:Contest={} ){ let time = new Date().toISOString() console.log(time , message , context.appId ) } messageLog1('User clicked on button',{ appId:'iphone X' }) // "2021-02-19T18:08:03.208Z", "User clicked on button", iphone X ``` <p class="warning">※ 如果移除預設值 Typescript 可以從預設值推論參數的型別</p> #### **2.其餘參數:** 當不確定數量的參數,並將其視為一個陣列 --- ### 類別class、繼承extends 與介面Interface <h5 class="tilte-bg">class</h5> 以 class 關鍵字宣告類別可以使用 extends 關鍵字來擴充方法。 <h5 class="tilte-bg">public </h5> class內部 & class外部任何地方都可以存取(預設存取層)。 <h5 class="tilte-bg">protected</h5> 跟與 private 相同只能可從此 class內部使用及繼承父class的其子類別的實體存取。 ``` class Game { protected content:string = "Game Instructions"; } class MarioGame extends Game { log(){ return this.content } } // OK ``` <h5 class="tilte-bg">private</h5> 儘可以從此類別的實體內部進行存取,也不允許繼承內部使用。 ``` class Game { private content:string = "Game Instructions"; } const MarioGame = new Game() MarioGame.content // 錯誤提示 content 為私有的 Property 'content' is private and only accessible within class 'Game'. ``` <h5 class="tilte-bg">readonly</h5> 代表初始值只能被讀無法寫入防止屬性被更改,和const差異 : 變數的話就用 const 、是屬性的話就用 readonly。 <h5 class="tilte-bg">abstract</h5> 意味無法直接實體化該類別,但依舊可以載此類別定義其他方法以及不同的業務邏輯 ``` abstract class Employee { abstract skill(){} } class Engineer extends Employee{ private description:string = 'Vue & JavaScript' skill(){ return `Skill is ${description}` } } class Designer extends Employee{ private description:string = 'HTML & CSS' skill(){ return `Skill is ${description}` } } ``` <h5 class="tilte-bg">super</h5> 可直接存取父類別的方法 <h5 class="tilte-bg">constructor</h5> 如果使用繼承 extends 子類別繼承父類別後要使用 constructor 必須加上 super() 並傳遞父類層 constructor 所需要的值,即使父類別沒有使用 constructor 子類別也必須加上 super() <h5 class="tilte-bg">Getter</h5> 關鍵字是 get , 如果變數是 private 外部想要存取使用可以用 get return <h5 class="tilte-bg">Setter</h5> 關鍵字是 set 可以在賦予值時做加工 <h5 class="tilte-bg">static</h5> ststic 靜態屬性不需要重新 new 對象就可以直接使用 #### **1.class:** ``` class Game { content = "Game Instructions"; start(){ return this.content } } const MarioGame = new Game() MarioGame.start() // Game Instructions ``` #### **2.extends:** ``` class RingFitGame(子) extends Game(父) { run(){ return 'Run 10 kilometers' } } const PokemonGame = new RingFit() PokemonGame.start() // Game Instructions PokemonGame.run() // Run 10 kilometers ``` #### **3.interface:** 一個介面可以擴充任何形狀,物件 / type / class / interface 都可以, 在同一個 scope 名稱相同的多個介面會自動被合併,同名的多個型別再編譯時發現錯誤 介面可以宣告實體特性,但他們不可以宣告可見的修飾詞 (public、protected、private)也不可以實立化(不能 new ) 並且不可以使用 static 關鍵字 ``` interface Food { calories:number tasty:boolean } or type Food = { calories:number tasty:boolean } ``` #### **4.constructor:** ``` class Game { public gameName:string; constructor(gameName:string){ this.gameName = gameName } } or class Game { constructor(public gameName:string){} } const RingFitGame = new Game('RingFitGame') console.log(RingFitGame) //Game: {"gameName": "RingFitGame"} class PokemonGame extends Game { constructor(public gameTime:number){ super('PokemonGame') } } const PokemonGame2 = new PokemonGame(100) console.log(PokemonGame2.gameName) // PokemonGame console.log(PokemonGame2.gameTime) // 100 ``` #### **5.Getter & Setter:** ``` class User { constructor(private _age:number){} get userAge(){ return this._age + 5 } set userAge(age:number){ this._age = age - 9 } } const UserA = new User(20) UserA.userAge = 30 console.log(UserA.userAge) // 26 ---> 經過封裝 30 + 5 - 9 ``` #### **6.static:** ``` class User { static getUserAge(age:number){ return `今年${age}歲` } } console.log(User.getUserAge(18)) // 今年18歲 ``` --- ### 子型別 & 超型別 ![](https://i.imgur.com/pGC1crt.jpg) --- ### 泛型 泛型的目的是在調用 function、泛型類、函數参數、函數返回值時必須處理各種數據類型時的約束。並利用"<>"中加上一個類型變量T(傳遞參數的型別)。 T代表類型,在定義泛型時通常用作第一個類型變量名。而T其實可以用任何有效的名稱替換並不限於一個類型變量()。 ``` function identities<T, U>(arg1: T, arg2: U): T { return arg1; } ``` 泛型可以使用於function, Class. Interface ``` class Person<T>{} 面 function Person<T>(arg: T): T {return arg;} interface Person<T> {} ``` 情境1:參數可以是 string | number (範例1) ``` 範例1: function join(first: string|number ,second: string|number){ return `first:${first} second:${second}` } console.log(join<string>('dora','test')) // "first:dora second:test" console.log(join<string>('dora',1)) // "first:dora second:1" or function join<T,P>(first:T ,second: P):string|number{ return `first:${first} second:${second}` } console.log(join<string,number>('dora',1)) // "first:dora second:1" ``` 情境2:參數限制型別需一致(範例2) ``` 範例2: function join<name>(first: name ,second: name){ return `first:${first} second:${second}` } console.log(join<string>('dora','test')) // "first:dora second:test" console.log(join<string>('dora',1)) Argument of type 'number' is not assignable to parameter of type 'string' ``` 情境3:泛型中參數為數組(範例3) ``` 範例3: function join<T>(parmas:T[]):T{ return parmas } or function join<T>(parmas:Array<T>):T{ return parmas } console.log(join<string>(['dora','test555'])) // ["dora", "test555"] ``` 泛型接口 ``` interface Identities<T, U> { id1: T; id2: U; } function identities<T, U> (arg1: T, arg2: U): Identities<T, U> { console.log(arg1 + ": " + typeof (arg1)); console.log(arg2 + ": " + typeof (arg2)); let identities: Identities<T, U> = { id1: arg1, id2: arg2 }; return identities; } ``` 類別泛型 ``` class List{ private data:any = []; push(element: any){ this.data.push(element) } pop(){ return this.data.pop() } } let addItem = new List(); addItem.push(5) console.log(addItem) // List: { "data": [5]} ``` --- ### 進階型別 #### **1.條件式型別:** 宣告依存 U 和 V 的型別 T 如果 U <: V 型別 T 就將指定給 A 否則則指定給 B, IsString 接受泛型 T ,條件是 T extends string (T 是 string 的子型別嗎?) ``` type IsString<T> = T extends string ? true : false ``` #### **2.分配式條件式型別:** 宣告依存 U 和 V 的型別 T 如果 U <: V 型別 T 就將指定給 A 否則則指定給 B, IsString 接受泛型 T ,條件是 T extends string (T 是 string 的子型別嗎?) ``` type ToArray<T> = T[] type A = ToArray <number>= number[] type B = ToArray <number | string>= (string|number)[] ``` #### **3.infer:** 如果 T 能賦值給(param: infer P) => any,則結果是(param: infer P) => any類型中的參數P,否則返回為T ``` type ParamType<T> = T extends (param: infer P) => any ? P : T; interface User { name: string; age: number; } type Func = (user: User) => void; type Param = ParamType<Func>; // Param = User type AA = ParamType<string>; // string ``` TypeScript 型別系統 型別推論(Type Inference) 沒有明確註記資料型別,TS 編譯器便會自動推論出資料型別 型別斷言(Type Assertion) TS 允許開發者覆蓋它的推論,這樣的機制稱為「型別斷言」。編譯器會接受開發者手動寫下的斷言,並且不會再送出警告錯誤。 型別斷言有兩種寫法: 第一種是<型別>值 (angle-bracket <>)寫法 ``` let code: any = 123; let employeeCode = <number> code; ``` 第二種則是值 as 型別 (as keyword)寫法 ``` let code: any = 123; let employeeCode = code as number; ``` 型別註解(Type Annotation) 透過手動註解的方式,明確宣告資料型別,告訴編譯器必須符合註解的型別,以方便在開發時就抓到變數的錯誤賦值問題。在做型別註解時,會在變數、參數或屬性後面加上冒號:型別,冒號後方可加一個空格,例如 ``` const age: number = 32 ``` 型別註解告訴編譯器這個資料必須永遠都是這個型別 ; 而型別斷言則主要用在覆蓋 TS 編譯器自動進行的型別推斷和型別相容性規則(Type Compatibility),告訴編譯器你知道這個值要符合斷言的型別,從而避免了上面範例中型別不兼容的錯誤產生。 TypeScript 型別推論 TypeScript 會在沒有明確的指定型別的時候推測出一個型別,這就是型別推論。 如果定義的時候沒有賦值,不管之後有沒有賦值,都會被推斷成 any 型別而完全不被型別檢查: