要熟悉 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 新手指南