要熟悉 TypeScript 的型別訂定,並不容易,而且撰寫時也會花費較多的時間,但好處是減少程式碼的錯誤,也讓協作者更容易解讀對方撰寫的程式碼。 萬事起頭難,不同的地方就是多讀幾次,或是尋找相關的範例來相呼應!
Class 傳統方法中,JavaScript 透過建構函式實現類別的概念,透過原型鏈實現繼承。而在 ES6 中,我們終於迎來了 class。
定義一切事務的抽象特點
object :class 的實例 new Class 生成
物件導向 OOP(Object Oriented Programming) :三大特性 封裝、繼承、多型
封裝(Encapsulation):將對資料的操作細節隱藏起來,只暴露對外的介面。外界呼叫端不需要(也不可能)知道細節,就能透過對外提供的介面來訪問該物件,同時也保證了外界無法任意更改物件內部的資料
繼承(Inheritance):子類別繼承父類別,子類別除了擁有父類別的所有特性外,還有一些更具體的特性
多型(Polymorphism):由繼承而產生了相關的不同的類別,對同一個方法可以有不同的響應複習下 ES6 撰寫方式
使用 class 定義類別,使用 constructor 定義建構函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Animal { constructor (name ){ this .name = name; } run () { return `${this .name} is running` } } const snake = new Animal ('lily' )console .log (snake.run ()); class Dog extends Animal { bark ( ) { return `${this .name} is barking` } } const bao = new Dog ('bao' )console .log (bao.run ()) console .log (bao.bark ())
使用 extends 關鍵字實現繼承
这里我们重寫構造函式,注意在子類的構造函式中,必須使用 super 調用父類的方法,否則會報錯。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Cat extends Animal { constructor (name ) { super (name) console .log (this .name ) } run ( ) { return 'Meow, ' + super .run () } } const maomao = new Cat ('maomao' )console .log (maomao.run ())
TypeScript 中的 class TypeScript 可以使用三種訪問修飾符(Access Modifiers)
public 修飾的屬性或方法是公有的,可以在任何地方被調用到,預設所有的屬性和方法都是 public 的
private 修飾的屬性或方法是私有的,不能在声明它的类的外部調用
protected 修飾的屬性或方法是受保护的,它和 private 類似,區别是它在子類中也是允许被訪問的 例子說明:
name 被設定為了 public,所以直接訪問實例的 name 屬性是允許的。
1 2 3 4 5 6 7 8 9 10 11 class Animal { public name; public constructor (name ) { this .name = name; } } let a = new Animal ('Jack' );console .log (a.name ); a.name = 'Tom' ; console .log (a.name );
希望有的屬性是無法直接存取的,這時候就可以用 private 了:
name 設為 private
1 2 3 4 5 6 7 8 9 10 11 12 13 class Animal { private name; public constructor (name ) { this .name = name; } } let a = new Animal ('Jack' );console .log (a.name ); a.name = 'Tom' ;
使用 private 修飾的屬性或方法,在子類別中也是不允許訪問的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Animal { private name; public constructor (name ) { this .name = name; } } class Cat extends Animal { constructor (name ) { super (name); console .log (this .name ); } }
而如果是用 protected 修飾,則允許在子類別中訪問:
將上述程式碼修改一下
1 2 3 4 5 6 class Animal { protected name; public constructor (name ) { this .name = name; } }
類別 class 的型別 給類別加上 TypeScript 的型別很簡單,與介面類似:
1 2 3 4 5 6 7 8 9 10 11 12 class Animal { name : string ; constructor (name: string ) { this .name = name; } sayHi (): string { return `My name is ${this .name} ` ; } } let a : Animal = new Animal ('Jack' );console .log (a.sayHi ());
類別
class 與 interface
介面(Interfaces):不同類別之間公有的屬性或方法,可以抽象成一個介面。介面可以被類別實現(implements)。一個類別只能繼承自另一個類別,但是可以實現多個介面
在 物件導向 世界中,一個 class 只能繼承自另外一個class
有時候不同 class 之前,可以有共同的特性,使用子類繼承父類的方法很難來完成
class 可以使用 implements 來實現 interface (提高 OOP 的靈活性)
範例一:
有 車子和手機,都有打開radio的功能
可以將兩個共有的抽取為一個 interface
1 2 3 4 5 6 7 8 9 10 11 class Car { switchRadio (trigger: boolean ) { } } class Cellphone { switchRadio (trigger: boolean ) { } }
可以將兩個共有的抽取為一個 interface
void => 代表什麼都不返回
在class 後面放入 implements 讓類去實現它
1 2 3 4 5 6 7 8 9 10 11 12 13 interface Radio { switchRadio (trigger : boolean ): void ; } class Car implements Radio { switchRadio (trigger ) { return 123 } } class Cellphone implements Radio { switchRadio ( ) { } }
範例二
新特性為 檢查電池的容量
是手機有,但汽車沒有的
所以新建立一個 interface (檢查battery)
1 2 3 4 5 6 7 8 9 10 11 interface Battery { checkBatteryStatus (): void ; } class Cellphone implements Radio , Battery { switchRadio ( ) { } checkBatteryStatus ( ) { } }
此外 interface 之間有繼承關係
建立 radioWithBattery 繼承 Radio ,裡面再放入檢查電量的設定
1 2 3 4 5 6 7 8 9 10 11 12 interface radioWithBattery extends Radio { checkBatteryStatus (): void ; } class Cellphone implements radioWithBattery { switchRadio ( ) { } checkBatteryStatus ( ) { } }
類別與介面
enum 列舉
常數指執行程序中不會被改變的值,在 JS 中我們一般會用 const 來宣告
但有些取值是在一定範圍內的一系列常數。
如:一周內七天、三原色(紅、黃、藍)、方向(上、下、左、右)範例一
數字列舉
列舉成員會被賦值為從0,開始遞增
1 2 3 4 5 6 7 8 9 10 11 enum Direction { Up , Down , Left , Right , } console .log (Direction .Up ) console .log (Direction [0 ])
可以手動賦予值
未手動賦值的列舉項會接著上一個列舉項遞增。
1 2 3 4 5 6 7 8 9 10 Up = 10 , Down , Left , Right , } console .log (Direction .Down ) console .log (Direction [0 ])
範例二 1 2 3 4 5 6 7 8 9 10 11 enum Direction { Up = 'UP' , Down = 'DOWN' , Left = 'LEFT' , Right = 'RIGHT' , } const value = 'UP' if (value === Direction .Up ) { console .log ('go up!' ) }
範例三
常數列舉
編譯後的邏輯變少了
使用常數列舉會內連列舉的用法並且不會將設定的列舉編譯成 JS 程式碼
1 2 3 4 5 6 7 8 9 10 const enum Direction { Up = 'UP' , Down = 'DOWN' , Left = 'LEFT' , Right = 'RIGHT' , } const value = 'UP' if (value === Direction .Up ) { console .log ('go up!' ) }
泛型 Generics
要解決什麼問題
建立函式 echo 參數為 arg ,接著宣告變數傳入參數
但其中的型別沒有設定
傳入和返回的型別無法統一
1 2 3 4 5 function echo (arg ) { return arg } const result = echo (123 )
當在建構 function、internet及Class 時,你會希望這些component都能被重複運用的 Generic(泛型)提供了一個彈性的作法。
語法是: <T>
範例一
泛型(Generics)是指在定義function、internet及Class的时候,不预先指定具體的型別,而在使用的时候再指定型別的一種特性。
1 2 3 4 5 function echo<T>(arg : T): T { return arg } const result = echo (123 )
範例二 傳入多個 1 2 3 4 5 6 function swap<T, U>(tuple : [T, U]): [U, T] { return [tuple[1 ], tuple[0 ]] } const result = swap (['string' , 123 ])
泛型第二部分 - 约束泛型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function echoWithArr<T>(arg : T): T { console .log (arg.length ) return arg } interface IWithLength { length : number ; } function echoWithLength<T extends IWithLength >(arg : T): T { console .log (arg.length ) return arg } echoWithLength ('str' )const result3 = echoWithLength ({length : 10 })const result4 = echoWithLength ([1 , 2 , 3 ])echoWithLength (7 );
泛型第三部分 - 泛型在 class 和 interface 中的使用
泛型在 class 的使用
在第一個程式中存在一个问题,它允許你向 Queue 中添加任何型別的數據,當然,當數據被彈出隊列时,也可以是任意類型
在下方的範例中,看起来可以向隊列中添加 string 型別的數據,但是那麼在使用的過程中,就會出現無法捕捉的錯誤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Queue { private data = []; push (item ) { return this .data .push (item) } pop ( ) { return this .data .shift () } } const queue = new Queue ()queue.push (1 ) queue.push ('str' ) console .log (queue.pop ().toFixed ())console .log (queue.pop ().toFixed ())
1 2 3 4 5 6 7 8 9 10 11 12 class Queue <T> { private data = []; push (item: T ) { return this .data .push (item) } pop (): T { return this .data .shift () } } const queue = new Queue <number >()
泛型在 interface 中的使用
之前提過可以使用介面的方式來定義一個函式需要符合的形狀
當然也可以使用含有泛型的介面來定義函式的形狀:
1 2 3 4 5 6 7 8 9 10 11 interface KeyPair <T, U> { key : T; value : U; } let kp1 : KeyPair <number , string > = { key : 1 , value : "str" }let kp2 : KeyPair <string , number > = { key : "str" , value : 123 }let arr :number [] = [1 ,2 ,3 ];let arrTwo :Array <number > = [1 ,2 ,3 ]
interface 搭配泛型之后,可以靈活的返回不同的型別
創建一个拥有特定型別的容器,class 和 泛型 仿佛给一个容器貼上標籤一样
泛型就好像一个可變的參數,在用的时候傳入,生成这個不同型別的一个容器,
上個部分的用它来靈活的约束参数的型別,不需要參數是一个特别死板的型別,不希望他是一个特定 string、number 型別,我要傳入的参數必须有某某屬性、某某方法,否則就會報錯。
在函式使用的时候,函式的这个型別推斷,不會進入到函式中,所以使用表達式,没法明確建立型別的绑定,用泛型可以让我们打破這個鴻溝,這個时候就可以返回它傳入的類型。
Type Aliases
就是给型別起一個别名,讓它可以更方便的被重用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let sum : (x: number , y: number ) => number const result = sum (1 ,2 )type PlusType = (x: number , y: number ) => number let sum2 : PlusType type StrOrNumber = string | number let result2 : StrOrNumber = '123' result2 = 123 type Directions = 'Up' | 'Down' | 'Left' | 'Right' let toWhere : Directions = 'Up'
我們使用 type 定了一個字串字面量型別 EventNames,它只能取三種字串中的一種。 注意,型別別名與字串字面量型別都是使用 type 進行定義。
1 2 3 4 5 6 7 8 9 type EventNames = 'click' | 'scroll' | 'mousemove' ;function handleEvent (ele: Element, event: EventNames ) { } handleEvent (document .getElementById ('hello' ), 'scroll' ); handleEvent (document .getElementById ('world' ), 'dbclick' );
Intersection Types
使用 &
符號
經過上面的 type 使 IName 就有了 name 和 age兩個屬性在裡面
和interface 的 extends 有點類似,都是為了實現物件形狀組合和擴展
1 2 3 4 5 6 interface IName { name : string } type IPerson = IName & { age : number }let person : IPerson = { name : 'hello' , age : 12 }
什麼时候用介面,什麼时候用 Type Aliases:
interface 是 docker typing 的實現方式,是一種獨特類型,和extends class implememts 有關的用interface ,
和交叉,聯合型別的有關的用 Type Aliases;
內建物件 DOM 和 BOM 的內建物件 DOM 和 BOM 提供的內建物件有: Document、HTMLElement、Event、NodeList 等。 TypeScript 中會經常用到這些型別:
1 2 3 4 5 let body : HTMLElement = document .body ;let allDiv : NodeList = document .querySelectorAll ('div' );document .addEventListener ('click' , function (e: MouseEvent ) { });
ECMAScript 的內建物件
Boolean、Error、Date、RegExp 等。
1 2 3 4 let b : Boolean = new Boolean (1 );let e : Error = new Error ('Error occurred' );let d : Date = new Date ();let r : RegExp = /[a-z]/ ;
TypeScript 內置
Utility Types 官方 Typescript 還提供了一些功能性,帮助性的型別,這些型別,大家在 JS 的世界是看不到的,這些型別叫做 utility types,提供一些簡潔明快而且非常方便的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface IPerson { name : string age : number } let viking : IPerson = { name : 'viking' , age : 20 }type IPartial = Partial <IPerson >let viking2 : IPartial = { } type IOmit = Omit <IPerson , 'name' >let viking3 : IOmit = { age : 20 }
資料來源:
線上課程:實戰 Vue3.0(正式版) + TS
TypeScript 新手指南