Typescript === ==參考資料:== --- https://willh.gitbook.io/typescript-tutorial/ https://www.typescriptlang.org/docs/handbook/intro.html ==原始資料型別:== --- ::: info boolean (布林值)、number (數值)、string (字串)、null、undefined 、Symbol。 ::: - ### Boolean ```javascript= let Simple: boolean = false; ``` - ### Number ```javascript= let Num: number = 6; // 支援二進位制表示法 let Num: number = 0xf00d; // 支援二進位制表示法 let binaryLiteral: number = 0b1010; ``` - ### String ```javascript= let myName: string = 'Zoe'; let lunch: number = 100; // 範本字串 let Simple: string = `Hello, my name is ${myName}. I spend ${lunch} dollars for lunch`; ``` - ### void 空值 , 用來宣告一個沒有return值的函數 ```javascript= let GreetThree :(a :string, b: string) => void ; GreetThree = (name: string, greeting : string) => { console.log(`${name} says ${greeting}`) } ``` - ### Null & Undefined - **vs void** : **undefined 和 null 是所有型別的子型別**。 - undefined 和 null 可以賦值給其他型別的變數,但void卻不行。 ```javascript= let num: number = undefined; //ok let n :undefined; let num: number = n; //ok let v : void; let num: number = v; // Error : Type 'void' is not assignable to type 'number'. ``` <br> <br> ==Any、Array、Tuple、Enum== --- - ### Any ```javascript= //使用any賦值後,可任意更改成任何type //宣告一個變數為任意值之後,對它的任何操作,返回的內容的型別都是任意值。 let anyAge : any = 30; //number anyAge = true; //boolen console.log(anyAge) //true anyAge = "hello" console.log(anyAge) //'hello' let mixedarray : any[] = [] mixedarray.push("30"); mixedarray.push(30); mixedarray.push(false); console.log(mixedarray) //['30',30,fasle] //可呼叫任何方法,傳回的值不論任何型別都可以: let anyThing: any = 'Tom'; anyThing.setName('Jerry'); anyThing.setName('Jerry').sayHello(); anyThing.myName.setFirstName('Cat'); //在一開始宣告變數未給定型別即視為any let something; something = 'seven'; //string -> ok something = 7; //重新賦值number -> ok ``` - ### Array - 三種宣告方式 1. dataTyp[] 2. Array<dataType> 3. [dataType] ```javascript= const user: string[] = ["aa", "bb", "cc"]; const idNumber: Array<number> = [1, 2, 3]; const mix: [string,number] = ['hi', 3]; ``` <br> ```javascript= let number = [10,20,30] number.push(30); number.push("a"); // Error, 遵循第一次建立的type let mix: Array<string | number | boolen> = [10, 'array' , true] let str : Array<string> = ['123'] mix.push(20) mix.push('hello') //both OK //用泛型宣告陣列 let mixArray: Array<number> = [1, 1, 2, 3, 5]; ``` - ### Tuple ```javascript= //一旦宣告賦值需提供指定的項 let tom: [string, number]; tom = ['Tom'] //Error : required in type '[string, number]'. tom = ['Tom', 25]; //OK ``` - ### Enum ```javascript= //未給值狀況下可像陣列一樣的用0~最尾數去更改或讀值。 Ex1. enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 0); // true console.log(Days["Tue"] === 2); // true console.log(Days[0] === "Sun"); // true console.log(Days[6] === "Sat"); // true Ex2. //增加可讀性溝通 enum PodCast { Success : 1, Fail : -1, Streaming : 0 } const status = PodCast.Succsss console.log(status) ``` <br> <br> ==union types 聯合型別== --- ```javascript= let mixed : (string|number|boolean)[] = [] mixed.push("hello") //ok mixed.push(20) //ok mixed.push(true) //ok function getLength(something: string | number): number { return something.length; } //Error : Property 'length' does not exist on type 'number'. //傳進去的值可能為 string or number, length不為number的屬性 //加上判斷式 function getLength(something: string | number): number { if(typeof something === "string"){ return something.length; }else{ return something } } let myFavoriteNumber: string | number; myFavoriteNumber = 'seven'; console.log(myFavoriteNumber.length); // 5 myFavoriteNumber = 7; console.log(myFavoriteNumber.length); // 報錯 ``` <br> <br ==型別推論(Type Inference)、型別註記(Type Annotation)和型別斷言(Type Assertion)== --- - 型別推論 : 沒有明確註記資料型別,TS編譯器便會自動推論出資料型別。 ![](https://i.imgur.com/SZlkjkZ.png) ![](https://i.imgur.com/nQylPlh.png) - 型別註記 : 明確的宣告資料型別,告訴編譯器必須符合註記的型別,在開發時就抓到錯誤賦值。 ![](https://i.imgur.com/brrlqzn.png) ==斷言 As== --- - TS認為開發者更了解自己編寫的程式碼。因此允許開發者覆蓋它的推論。編譯器會接受開發者寫下的斷言,並且不會再送出警告錯誤。 - 兩種用法 - <型別>值 - 值 as 型別 - 不能強制斷言成原本不存在的型別,必須透過將型別斷言成unknown,再行斷言。 ![](https://i.imgur.com/aFBH1SR.png) - 和型別推論不同之處 - ==Type annotation== makes sure that the ==exact type== match. - ==Type assertion== makes sure that the ==subtype== match. ex. ![](https://i.imgur.com/muKms3s.png) ### 適用1. 將變數指定為更加具體的型別(when you need to narrow down the type) ```javascript= Ex1. function getLength(something: string | number): number { if ((something as string)) { return (something as string).length; } else { return something.toString().length; } } Ex2. interface Cat { name: string; run(): void; } interface Fish { name: string; swim(): void; } function isFish(animal: Cat | Fish) { if (typeof (animal as Fish).swim === 'function') { return true; } return false; } ``` Ex3. ![](https://i.imgur.com/gwb4QTV.png) ### 適用2. 當函式回傳any type,用斷言將any轉換成一個精確的型態會是比較好的做法。 ```javascript= //old function getCacheData(key: string): any { return (window as any).cache[key]; } //new function getCacheData(key: string): any { return (window as any).cache[key]; } interface Cat { name: string; run(): void; } const tom = getCacheData('tom') as Cat; tom.run(); ``` ### 適用3. 使用第三方的資源(Third-party resources)、呼叫會回傳未知結果的函示等。 ```javascript= //宣告fetch回來的值的type interface Data { name:string, age:number, uid:number } async function getData(){ const res = await fetch('http://jsonplaceholder.typicode.com/todos/1') const data = await res.json() as Data } ``` ### phoenix ![](https://i.imgur.com/oPlE4Py.png) ![](https://i.imgur.com/2vt1bNZ.png) ### As 壞處 1. 編譯器不會提醒你要加入的properties ```javascript= //use 型別註記 interface Foo { bar: number; bas: string; } var foo: Foo = { // the compiler will provide autocomplete for // properties of Foo }; //use Assertion interface Foo { bar: number; bas: string; } var foo = {} as Foo; // ahhhh .... forget something? ``` 2. 斷言取代type check,無法得知正確型態。 ```javascript= type Human = { age: number name: string spokenLanguage: string } let human = { age: 18, spokenLanguage: "English" }; const human2 = human as Human; console.log(human2.name.toUpperCase()); //Error: Cannot read property 'toUpperCase' of null ``` - 參考資料: 1. https://medium.com/@bsalwiczek/type-annotation-vs-assertion-in-typescript-one-important-difference-4f4df715b5fe 2. https://stackoverflow.com/questions/47994926/detailed-differences-between-type-annotation-variable-type-and-type-assertion 3. https://ithelp.ithome.com.tw/articles/10217384 4. https://basarat.gitbook.io/typescript/type-system/type-assertion 5. https://medium.com/%E5%89%B5%E9%A0%86%E7%A7%91%E6%8A%80/%E7%AD%86%E8%A8%98-typescript-assertion-18fc95b69d9a <br> <br> ==Type & Interface== --- - ### Type - 可以用來宣告Primitive type及物件、函式 ```javascript= type Name = string; type NameResolver = () => string; type NameOrResolver = Name | NameResolver; function getName(n: NameOrResolver): Name { if (typeof n === 'string') { return n; } else { return n(); } } type User = { name: string age?: number }; type SetUser = (name: string, age: number)=> void; ``` - ### Type - 可以用來宣告物件、函式 ```javascript= interface User { name: string age: number } interface SetUser { (name: string, age: number): void; } interface Alarm { alert(); } ``` - ### Type & Interface - 繼承寫法不同 - 宣告類型差異 - https://ms0223900.medium.com/typescript-%E4%B8%8D%E8%83%BD%E4%B8%8D%E7%9F%A5%E9%81%93%E7%9A%84%E5%8A%9F%E8%83%BD-ed020f4f7ef3 ```javascript= //type extends type type myName = { name:string; } type myAge = myName & { age:number; } //interface extends interface interface myName { name:string; } interface myAge extends myName { age: number; } // type extends interface interface myName { name: string; } type myAge = myName & { age: number; } //interface extends type type myName = { name:string; } interface myAge extends myName { age:number; } ``` ```javascript= //type 可以宣告聯合類型 interface myName { ...; } interface myAge { ...; } type Pet = myName | myAge ; // type可以選告基本類型 type Name = string //interface 實現擴充 interface User { name: string age: number } interface User { sex: string } // 可以用 implements 被class多重實現 interface Alarm { alert(); } interface Light { lightOn(); lightOff(); } class Car implements Alarm, Light { alert() { ...; } lightOn() { ...; } lightOff() { ...; } } ``` <br> <br> ==Function== --- - 函式表示式(Function Expression) ```javascript= let greeting : Function greeting = () => { console.log("hello") } const add = (a :number, b :number, c :number|string = 10) => { console.log(a+b) console.log(c) } add(8,9) //17 10 沒有傳入第三個參數,預設數字為10 add(8,9,20) //17, 20 add(8,9,'20') //17 '20' ``` - 函式宣告(Function Declaration) ```javascript= function wrapInArray(obj: string | string[]) { if (typeof obj === "string") { return [obj]; }else{ return obj; } } console.log(wrapInArray('1223')) //['1223'] console.log(wrapInArray(['123'])) //['123'] ``` - 可選(==需寫在最後一個參數==) ```javascript= function Person(Name: string, Age: number , Gender?: string) { if (Name) { return Name + 'is ' + Age + 'years old'; } else { return Name + 'is a' + Age + 'years old' + Gender; } } let tomcat = Person('Tom', 10); let tom = Person('Tom', 20, 'boy'); console.log(tomcat); //Tom is 10 years old console.log(tom); //Tom is a 20 years old boy ``` - Function Signature : 定義function中傳進去的參數跟回傳的type屬性 ```javascript= let logDetails : (obj:{name:string, age:number}) => void; type person = {name: string , age:number} ; logDetails = (obj:person) => { console.log(`${obj.name} is ${obj.age} years old`) } logDetails({name:"zoe", age:20}) // zoe is 20 years old logDetails({name:"John", age:30}) // John is 30 years old ``` <br> <br> ==Object== --- ```javascript= let zoeOne : object; zoeOne = { name : "zoecheng", gender : "female", } let zoeTwo : { name : string, age : number } zoeTwo = {name:"Jack", age:30} ``` <br> <br> ==Class== --- - 實現 interface ```javascript= //interface file export interface Hasformatter{ format() :string; } ``` ```javascript= // implements to class interface Hasformatter{ format() :string; } class Payment implements Hasformatter{ name : string ; details : string; amount : number; constructor(a: string,b: string, c:number){ this.name =a; this.details=b; this.amount=c } format(){ return `${this.name} is spend ${this.amount} for ${this.details}`; } } let a = new Payment('Zoe','shopping',300); console.log(a.format()) //Zoe is spend 300 for shopping ``` - public : 預設,class內外皆可以存取及修改值。 ```javascript= //public class Animal { public name; public constructor(name) { this.name = name; } } let a = new Animal("Dog"); console.log(a.name); // Dog,可以在class外部存取 a.name = 'Cat'; console.log(a.name); // Cat,也可以在class外部修改值 ``` <br> - private : ==只能==在宣告它的類別的==內部==讀取但及修改。 ```javascript= //privite class Property { private firstName: string; constructor() { this.firstName = 'Jimmy'; } } const propertyInstance = new Property(); console.log(propertyInstance.firstName); //只能在Property中存取 ``` <br> - protected :和private區別是它在==子類別==中也是允許被讀取及修改的。 ```javascript= //protected class Property { protected firstName: string; constructor() { this.firstName = 'Jimmy'; } } class PropertyTwo extends Property { sayHi(){ console.log(this.firstName) return this.firstName } } const propertyInstance = new Property(); const propertyTwoInstance = new PropertyTwo(); console.log(propertyInstance.firstName); //Error :只能在Property中存取 console.log(propertyTwoInstance.sayHi()); //Line: 13 Jimmy ``` <br> <br> ==Generics 泛型== --- - 定義function、interfaces或class的時候,不一開始指定傳入傳出型別,用<T>取代指定型別,在傳入時才指定型別。 ```javascript= function ArrayOne<T>(length: number, value: T): Array<T> { let result: T[] = [] for (let i = 0; i < length; i++) { result[i] = value; } return result; } console.log(ArrayOne<string>(3, 'x')) // ['x', 'x', 'x'] console.log(createArray(5, 3)); //[3,3,3,3,3] ``` ```javascript= // 泛型 + interface interface ArrayTwo { <T>(length: number, value: T): Array<T>; } let CreateArray: ArrayTwo; CreateArray = function<T>(length: number, value: T): Array<T> { let result: T[] = []; for (let i = 0; i < length; i++) { result[i] = value; } return result; } console.log(CreateArray(3, 'x')); // ['x', 'x', 'x'] ``` ```javascript= //當指定<T> 當型別時,一開始不知道卻謝型別無法使用length屬性 //需加上interface使涵蓋length屬性才適用arg.length interface Length { length : number } function StringLength <T extends Length>(arg:T): number { console.log(arg.length) // 4 return(arg.length) } StringLength("test") ``` <br> <br>