--- tags: Programing, Typescript --- # TypeScript 學習筆記 [![hackmd-github-sync-badge](https://hackmd.io/Z1bsxv0jSSWEQ8_rwXGN7A/badge)](https://hackmd.io/Z1bsxv0jSSWEQ8_rwXGN7A) [TOC] # `第一章` Basic types # `第二章` Interfaces # `第三章` Funcions # `第四章` Literal Types # `第五章` Union and Intersection Types <font size=5>透過Intersection 以及 Union 可以提供一個方式使我們能夠組成types</font> ## Union <font size=5>當遇到一個函式需要number以及string時,如:</font> ```typescript= /** * Takes a string and adds "padding" to the left. * If 'padding' is a string, then 'padding' is appended to the left side. * If 'padding' is a number, then that number of spaces is added to the left side. */ function padLeft(value: string, padding: any) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value; } if (typeof padding === "string") { return padding + value; } throw new Error(`Expected string or number, got '${padding}'.`); } padLeft("Hello world", 4); // returns " Hello world" ``` <font size=5> 在上面的狀況下,若傳入的 padding 型別不是 number 或 string 仍然有效 改法如下 </font> ```typescript= /** * Takes a string and adds "padding" to the left. * If 'padding' is a string, then 'padding' is appended to the left side. * If 'padding' is a number, then that number of spaces is added to the left side. */ function padLeft(value: string, padding: string | number) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value; } if (typeof padding === "string") { return padding + value; } throw new Error(`Expected string or number, got '${padding}'.`); } padLeft("Hello world", 4); // returns " Hello world" ``` ## Unions with Common Fields <font size=5> 若有一個value為union type,我們只能存取這些type共同的members </font> ```typescript= interface Bird { fly(): void; layEggs(): void; } interface Fish { swim(): void; layEggs(): void; } declare function getSmallPet(): Fish | Bird; let pet = getSmallPet(); pet.layEggs(); // Only available in one of the two possible types pet.swim(); //Error happended ``` ## Discriminating Unions ```typescript= type NetworkLoadingState = { state: "loading"; }; type NetworkFailedState = { state: "failed"; code: number; }; type NetworkSuccessState = { state: "success"; response: { title: string; duration: number; summary: string; }; }; // Create a type which represents only one of the above types // but you aren't sure which it is yet. type NetworkState = | NetworkLoadingState | NetworkFailedState | NetworkSuccessState; ``` <font size=5> 在上面我們定義出三種State的type <br> 並且他們有一個共同的元素 state <br> 在這之後我們可以透過當前state的值來得知現在狀態為何 </font> ```typescript= type NetworkState = | NetworkLoadingState | NetworkFailedState | NetworkSuccessState; function networkStatus(state: NetworkState): string { // Right now TypeScript does not know which of the three // potential types state could be. // Trying to access a property which isn't shared // across all types will raise an error state.code; //error // Property 'code' does not exist on type 'NetworkState'. // Property 'code' does not exist on type 'NetworkLoadingState'. // By switching on state, TypeScript can narrow the union // down in code flow analysis switch (state.state) { case "loading": return "Downloading..."; case "failed": // The type must be NetworkFailedState here, // so accessing the `code` field is safe return `Error ${state.code} downloading`; case "success": return `Downloaded ${state.response.title} - ${state.response.summary}`; } } ``` ## Union Exhaustiveness Checking <font size=5> 當我們新增union type的型別時 我們會希望compiler能告訴我們曾經使用到的地方需要更新 <br> <br> 且若沒有使用到時,傳的值應該會與原先定義的不同 </font> ```typescript= type NetworkFromCachedState = { state: "from_cache"; id: string response: NetworkSuccessState["response"] } type NetworkState = | NetworkLoadingState | NetworkFailedState | NetworkSuccessState | NetworkFromCachedState; function logger(s: NetworkState) { switch (s.state) { case "loading": return "loading request"; case "failed": return `failed with code ${s.code}`; case "success": return "got response" } } ``` <font size=5> 透過never型別來偵測型別完不完整 </font> ```typescript= function assertNever(x: never): never { throw new Error("Unexpected object: " + x); } function logger(s: NetworkState): string { switch (s.state) { case "loading": return "loading request"; case "failed": return `failed with code ${s.code}`; case "success": return "got response"; default: return assertNever(s) } } ``` ## Intersection Types <font size=5> Intersection 語法為 & <br> 將多個type 合成為一個 大type <br> 且每個type有的member 大type都得涵蓋住 <br> 底下則是把共同的type取出來<br> 使得單元變得更小,模組化更容易<br> </font> ```typescript= interface ErrorHandling { success: boolean; error?: { message: string }; } interface ArtworksData { artworks: { title: string }[]; } interface ArtistsData { artists: { name: string }[]; } // These interfaces are composed to have // consistent error handling, and their own data. type ArtworksResponse = ArtworksData & ErrorHandling; type ArtistsResponse = ArtistsData & ErrorHandling; const handleArtistsResponse = (response: ArtistsResponse) => { if (response.error) { console.error(response.error.message); return; } console.log(response.artists); }; ``` # Class ## Classes <font size=5> typescript 也支援Class </font> ```typescript= class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } } let greeter = new Greeter("world"); ``` ## Inheritance <font size=5> 觀念相同就不花篇幅說了 </font> ```typescript= class Animal { move(distanceInMeters: number = 0) { console.log(`Animal moved ${distanceInMeters}m.`); } } class Dog extends Animal { bark() { console.log("Woof! Woof!"); } } const dog = new Dog(); dog.bark(); dog.move(10); dog.bark(); ``` ## Public, private, and protected modifiers ### public by default <font size= 5> 在上面的篇幅中,Class沒有特別宣告public<br> 但如果需要的話,仍然可以在變數前方增添public keyword </font> ```typescript= class Animal { public name: string; public constructor(theName: string) { this.name = theName; } public move(distanceInMeters: number) { console.log(`${this.name} moved ${distanceInMeters}m.`); } } ``` ### ECMAScript Private Fields <font size=5> 在 TypeScript 3.8 支援最新的JavaScript語法定義 private </font> ```typescript= class Animal { #name: string constructor(theName: string) { this.#name = theName } } new Animal("Cat").#name ``` ### Understanding TypeScript's `private` <font size=5> TypeScript 本身提供關鍵字 private 來宣告私有變數 </font> ```typescript= class Animal { private name: string; constructor(theName: string) { this.name = theName; } } new Animal("Cat").name; ``` <font size=5> private Class 繼承實例 </font> ```typescript= class Animal { private name: string; constructor(theName: string) { this.name = theName; } } class Rhino extends Animal { constructor() { super("Rhino"); } } class Employee { private name: string; constructor(theName: string) { this.name = theName; } } let animal = new Animal("Goat"); let rhino = new Rhino(); let employee = new Employee("Bob"); animal = rhino; animal = employee; // 會出錯 兩者皆有自己的 name ``` ### Understanding `protected` <font size=5> 宣告為protected的變數<br> 在繼承後的subclass能取出<br> </font> ```typescript= class Person { protected name: string; constructor(name: string) { this.name = name; } } class Employee extends Person { private department: string; constructor(name: string, department: string) { super(name); this.department = department; } public getElevatorPitch() { return `Hello, my name is ${this.name} and I work in ${this.department}.`; } } let howard = new Employee("Howard", "Sales"); console.log(howard.getElevatorPitch()); console.log(howard.name); ``` ### Readonly modifier <font size=5> Readonly 變數在initial的時候就應該賦予值<br> 只有在 宣告時 以及 constuctor() 內可以賦予<br> 而 Readonly 的變數被視為public的 <br> 若要私有化必須加 private </font> ```typescript= class Octopus { readonly name: string; readonly numberOfLegs: number = 8; constructor(theName: string) { this.name = theName; } } let dad = new Octopus("Man with the 8 strong legs"); dad.name = "Man with the 3-piece suit";// 出錯 ``` ### Parameter properties <font size=5> 我們可以在constructor的參數名加上readonly<br> 這樣的話我們就可以減少原先版本宣告的那行了 </font> ```typescript= class Octopus { readonly numberOfLegs: number = 8; constructor(readonly name: string) {} } let dad = new Octopus("Man with the 8 strong legs"); dad.name; ``` ### Accessors <font size=5> 描述有關get() 跟 set() </font> ```typescript= const fullNameMaxLength = 10; class Employee { private _fullName: string = ""; get fullName(): string { return this._fullName; } set fullName(newName: string) { if (newName && newName.length > fullNameMaxLength) { throw new Error("fullName has a max length of " + fullNameMaxLength); } this._fullName = newName; } } let employee = new Employee(); employee.fullName = "Bob Smith"; if (employee.fullName) { console.log(employee.fullName); } ``` ### Static Properies <font size=5> `Static` 類別的靜態屬性 類別的靜態屬性與方法 不需要經由建構物件的過程 而是直接從類別本身提供的屬性與方法 稱之為靜態屬性與方法 或者被稱為靜態成員(Static Members) 具有固定、單一版本、不變的原則 通常會使用靜態成員的狀況: 1. 靜態成員不會隨著物件建構的不同而隨之改變 2. 靜態成員可以作為類別本身提供的工具,不需要經過建構物件的程序;換句話說:類別提供之靜態成員本身就是可被操作的介面 </font> ```typescript= class Grid { static origin = { x: 0, y: 0 }; calculateDistanceFromOrigin(point: { x: number; y: number }) { let xDist = point.x - Grid.origin.x; let yDist = point.y - Grid.origin.y; return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale; } constructor(public scale: number) {} } let grid1 = new Grid(1.0); // 1x scale let grid2 = new Grid(5.0); // 5x scale console.log(grid1.calculateDistanceFromOrigin({ x: 10, y: 10 })); ``` ### Abstract Classes <font size=5> 描述Class藍圖不需要特地實作 </font> ```typescript= abstract class Animal { abstract makeSound(): void; move(): void { console.log("roaming the earth..."); } } ``` ### Advanced Techniques #### Constructor Functions <font size=5> Because classes create types, you can use them in the same places you would be able to use interfaces. </font> ```typescript= class Point { x: number; y: number; } interface Point3d extends Point { z: number; } let point3d: Point3d = { x: 1, y: 2, z: 3 }; ``` # Enums ## Numeric enums <font size=5> 常見於其他程式語言中 </font> ```typescript= enum Direction { Up = 1, Down, Left, Right } ``` <font size=5> 在使用上也十分容易 </font> ```typescript= enum UserResponse { No = 0, Yes = 1 } function respond(recipient: string, message: UserResponse): void { // ... } respond("Princess Caroline", UserResponse.Yes); ``` ## String enums <font size=5> 觀念相同,只是需要傳入的值為string </font> ```typescript= enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT" } ``` ## Heterogeneous enums <font size=5> enums 可以把上面的混用,但這樣的行為不建議 </font> ```typescript= enum BooleanLikeHeterogeneousEnum { No = 0, Yes = "YES" } ``` ## Computed and constant members <font size=5> 如果enum的第一個元素沒有initializer 則,他的值會為0 </font> ```typescript= // E.X is constant: enum E { X } // All enum members in 'E1' and 'E2' are constant. enum E1 { X, Y, Z } enum E2 { A = 1, B, C } ``` <font size=5> It is a compile time error for constant enum expressions to be evaluated to NaN or Infinity. </font> ```typescript= enum FileAccess { // constant members None, Read = 1 << 1, Write = 1 << 2, ReadWrite = Read | Write, // computed member G = "123".length } ``` ## Union enums and enum member types <font size=5> 用Enum來當Type </font> ```typescript= enum ShapeKind { Circle, Square } interface Circle { kind: ShapeKind.Circle; radius: number; } interface Square { kind: ShapeKind.Square; sideLength: number; } let c: Circle = { kind: ShapeKind.Square, // 將會出錯, kind應該是Circle radius: 100 }; ``` ## Enums at runtime ## Enums at compile time <font size=5> 用keyof typeof來獲得Enum的Type時,會發現全部的keys都用string表示 </font> ```typescript= enum LogLevel { ERROR, WARN, INFO, DEBUG } /** * This is equivalent to: * type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG'; */ type LogLevelStrings = keyof typeof LogLevel; function printImportant(key: LogLevelStrings, message: string) { const num = LogLevel[key]; if (num <= LogLevel.WARN) { console.log("Log level key is:", key); console.log("Log level value is:", num); console.log("Log level message is:", message); } } printImportant("ERROR", "This is a message"); ``` ## Reverse mappings <font size=5> 除了順向的從Enum取值外,也可以逆向的傳值獲得key的名稱 </font> ```typescript= enum Enum { A } let a = Enum.A; let nameOfA = Enum[a]; //'A' ``` <font size=5> Keep in mind that string enum members do not get a reverse mapping generated at all. </font> ## const enums ## Ambient enums <font size=5> 奇葩的寫法XD </font> ```typescript= declare enum Enum { A = 1, B, C = 2 } ``` # Generics ## Hello World of Generics <font size=5> 很久之前看到的東西了,基本上他會去抓取使用者傳進來的參數type<br> 然後create出一個同樣參數型別的function </font> ```typescript= function identity<T>(arg: T): T { return arg; } ``` <font size=5> 上面這段函式,只要使用者輸入甚麼型別,他回傳的就是該型別<br> 因此可以方便我們去傳遞從其他地方來的型別成為我們要的<br> 以下為實際應用狀況 </font> ```typescript= let output = identity<string>("myString"); // ^ = let output: string ``` ```typescript= let output = identity("myString"); // ^ = let output: string ``` ## Working with Generic Type Variable ## Generic Types ```typescript= interface GenericIdentityFn { <T>(arg: T): T; } function identity<T>(arg: T): T { return arg; } let myIdentity: GenericIdentityFn = identity; ``` ## Generic Classes ```typescript= class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; } let myGenericNumber = new GenericNumber<number>(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function(x, y) { return x + y; }; ``` ## Generic Constraint <font size=5> 當我們要對Generic進行限制時,可透過建立出擁有必要元素的interface <br> 再透過Extends 去繼承他,如此一來便可以篩除不符合條件的參數了 </font> ```typescript= interface Lengthwise { length: number; } function loggingIdentity<T extends Lengthwise>(arg: T): T { console.log(arg.length); // Now we know it has a .length property, // so no more error return arg; } ``` ## Using Type Parameters in Generic Constraints <font size=5> 底下這個比較難理解,首先我們知道傳入的參數一個是Object 另一個則是 key <br> 因此在Generic 參數設定上, K extends T的key們 <br> 因此K的型別會屬於T這個Object內容物的型態 <br> 因此可確保傳入的obj以及key都是有效參數 <br> </font> ```typescript= function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]; } let x = { a: 1, b: 2, c: 3, d: 4 }; getProperty(x, "a"); // getProperty(x, "m"); ```