iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 12
1
Modern Web

讓 TypeScript 成為你全端開發的 ACE!系列 第 12

Day 12. 機動藍圖・介面宣告 X 使用介面 - TypeScript Interface Intro.

https://ithelp.ithome.com.tw/upload/images/20190916/20120614zKOz5izP1a.png

《機動藍圖》篇章概要

本系列第二部分:《機動藍圖》(The Agile Blueprint)篇章涵括的範圍就是 TypeScript 的重頭戲。不外乎,筆者想像中的內容大致上有這些(其實有些是筆者臨時打稿時想到可以講的部分XD),內容順序有可能會跳,不過還是給讀者稍微看過:

  • TypeScript Interface 介面
    • 宣告 Interface Declaration
    • Interface V.S. Type System
    • 複合型別 union & intersection
  • TypeScript Class 類別
    • Class 基礎 OOP (讀者若有 Java、C# 等完整實踐 OOP 語言的背景,這些東西對你們來說小事一碟~,不過對 JS 圈裡的初心者或者剛接觸 OOP 的人會有一些挑戰性,但理解過後還蠻好用的)
      • 建構子 Constructor
      • 類別屬性與方法 Member Variables(Properties) & Methods
      • 存取權限修飾子 Access Modifiers
      • 存取值方法 Accessors
    • Class 進階概念(初心者認為有些難的地方呢)
      • 繼承 Inheritance
      • 靜態屬性與方法 Static
      • 私有建構子與單例模式 Private Constructor & Singleton Pattern
      • 抽象類別 Abstract Class
    • Class + Inheritance v.s. Class + TS Interface
    • TS Class V.S. ES6 Class
  • 破壞 JS 圈(並非其他語言圈,就僅限 JS 圈!)裡對於這句話的重大誤解 (<-- 本系列文章重頭戲之一在這!)

    “Favor object composition over class inheritance.”

(讀者請不要誤會,筆者不是說這句話錯,這句話很對!但是被誤解到錯得很離譜,甚至還出現各種 Antipattern 讓筆者覺得不講也不行。)

原本還打算把 Generics 通用型別塞進本篇章,但回過頭來,除了感覺又會過大外,筆者認為在本章節系列內應該要好好把 TypeScript Interface 與 Class 部分講完,進度太快也會吃不消。

因此我們來進行本篇章第一篇,正文開始

介面的宣告與使用 TypeScript Interface

環境建置

在《前線篇章》系列,typescript-tutorial/01-basic 資料夾裡的 index.ts 內的程式碼有點過多。因此我們在新建另一個環境。開啟終端機進到 typescript-tutorial 裡面,並且建置新的資料夾(命名不限):

$ cd PATH_TO/typescript-tutorial
$ mkdir 02-interface-class

讀者應該熟悉了 TypeScript 的指令,一樣初始化 TS 編譯器設定檔:

$ tsc --init

先打開編譯器設定檔 —— 也就是 tsconfig.json,把 strictNullChecks 這個選項設定為 true(如圖二),筆者忘記在《前線維護》篇章寫到這句話,這是一個說小並不小,說大影響也不太大 —— 但依然會影響到後續文章的寫法。使用 VSCode 開啟 typescript-tutorial 這個資料夾你應該會看到類似這個畫面。(如圖一)

https://ithelp.ithome.com.tw/upload/images/20190916/20120614dKn3HV5xkB.png
圖一:新建 02-interface-class 的資料夾

https://ithelp.ithome.com.tw/upload/images/20190916/20120614UvSHswmcXL.png
圖二:將 tsconfig.json 裡的 strictNullChecks 改成 true

如果都已經建置完畢後,新增 index.ts 檔案在 typescript-tutorial/02-interface-class 的資料夾裡然後就開始囉~

宣告 Interface

讀者還記得型別化名(Type Alias)是什麼嗎? 如果還不記得的話,讀者請參見 Day 08. 的文章囉~不過應該還算簡單,筆者就二話不說把它的重點搬過來:

《前線維護・明文型別 X 格式為王》之 重點 2. 型別化名 Type Alias

若某型別 TT 可為任何的型別(包含原始型別、物件型別、TypeScript 內建型別、明文型別、複合型別、Generics 通用型別等)。其中我們想要讓該型別 T 等效於別名 A,則可以使用 TypeScript 的 type 關鍵字進行化名宣告:

type A = T;

型別化名的主要目的為簡化程式碼以及進行型別的抽象化(Type Abstraction)

型別化名的意義就是把複雜格式(尤其是明文格式)的型別進行程式碼簡化抽象化 —— 抽象化的概念一再地出現,非常重要呢!

而 TypeScript 的介面(Interface)與型別化名的作用有些相似,但可以把它想成更具彈性的型別

不過呢,儘管型別化名是可以用原始型別與各種廣義物件格式表示,譬如:

https://ithelp.ithome.com.tw/upload/images/20190916/20120614LyyyHn1rNj.png

貼心小提示

讀者可能覺得最後一個案例 —— 可以直接把字串的值當成一種型別並且 union 起來 —— 這也是明文型別(Literal Type)的一種形式。

通常看到的明文型別是物件的格式,但實際上單純原始型別的或廣義物件的都可以獨立成一個型別,這部分之前沒講到,不過筆者認為知道有這個功能就好。

然而,介面(Interface)跟型別化名可就不同了。

它只能以兩種形式以及混合的方式(Hybrid)呈現。筆者刻意把混合部分隔離出來,因為最後一種方式只是把前兩種形式進行混合罷了。

重點 1. TypeScript 介面(Interface)的定義與種類

TypeScript Interface 可以藉由關鍵字 interface 宣告出來,介面裡面的詳細定義可為:

  • 物件格式:即 JSON 格式,是為屬性對型別,不是對值
  • 單一函式格式:沒有任何屬性,就是函式而已,但不一定需要標上函式名稱
  • 混合格式:即『物件格式』與『單一函式格式』混合在一起

然而,在物件格式的介面定義下 —— 剛剛筆者在貼心小提示所講的 —— 如果你真的把值(比如字串當型別)寫下去,也是可以的!只是以後你註記某變數時使用了該介面,該變數必須強制把該屬性對應的值原封不動複製上去

而單一函式格式與混合格式會在後續進行補充。

但我們先看看前兩種版本的介面定義的形式,參見以下的範例。

https://ithelp.ithome.com.tw/upload/images/20190916/201206143QfyC3Ki1P.png

可以看出 UserInfo 把各種屬性對應的型別都描述出來了,很像在編列資料格式的型態。(筆者知道這是廢話XD)

然而,UpdateRecord 描述的單純就只有一個函式,它必須傳入的參數 —— 各自符合 number 型別以及 UserInfo 介面的參數進去。而該函式的輸出狀態為不輸出(也就是 void)。從這裡就可以看出,我們可以藉由 UpdateRecord 的介面定義一系列的修改 UserInfo 的函式,比如說更改名稱、更改性別、更改興趣等等。

筆者用一些例子展示給讀者看,註記介面的各種狀況。(以下程式碼檢測結果如圖三,錯誤訊息分別為圖四~圖六)

https://ithelp.ithome.com.tw/upload/images/20190916/20120614METoUd5On4.png

https://ithelp.ithome.com.tw/upload/images/20190916/20120614A2LckUsdqP.png
圖三:跟 type 作的型別化名概念很像,多一鍵、少一鍵與屬性對應型別錯誤都會錯

https://ithelp.ithome.com.tw/upload/images/20190916/201206143qujeQ3zB7.png
圖四:少一鍵 age 就被 TS 警告

https://ithelp.ithome.com.tw/upload/images/20190916/20120614wmS5MpkO4a.png
圖五:job 沒有存在在 Person 這個介面裡,因此多一鍵也會被 TS 警告

https://ithelp.ithome.com.tw/upload/images/20190916/201206142nApgYYYLU.png
圖六:hasPet 屬性必須接收 boolean,型別錯誤會被警告,不過感覺被警告也是挺正常

我們還有另外一種案例要測,那就是將變數或 JSON 物件格式傳入函式作為參數的案例。還記得在 Day 08. 裡我們檢測到狹義物件的明文格式作為參數的狀況嗎?上一次我們得出的最後結論是 —— 只要我們出現廣義物件時,會建議進行註記的動作,不然沒有註記過後的變數,帶入函式的參數,被檢測的限制會變輕

以下就是要測測看 Interface 有沒有類似的狀況。(TypeScript 檢測結果如圖七;錯誤訊息如圖八)

https://ithelp.ithome.com.tw/upload/images/20190916/20120614HcBS2GuoFl.png

https://ithelp.ithome.com.tw/upload/images/20190916/20120614RDZoIcHV9l.png
圖七:結果如果直接將物件的明文形式作為參數代入會被警告;然而,沒有對變數作積極註記,但看起來跟 Person 很像,就算通過

https://ithelp.ithome.com.tw/upload/images/20190916/20120614LKhCDrCh7x.png
圖八:恩,TypeScript 確實認為這個物件的明文格式比 Person 介面所定義的屬性多了一些,因此被警告

如果讀者仔細比對本篇用 interface 的結果與使用 type 的結果,幾乎完全一樣。

重點 2. 為註記之變數作為函式參數的行為

由於可以藉由存取物件的表現形式在某變數裡 —— 其中該變數沒有被積極註記 —— 只要該變數至少有符合介面的格式,依然可以通過函式參數對於變數的值的驗證。

但若想避免此狀況發生,任何變數需要存取物件時,必須進行積極註記型別或介面的動作

小結

讀者可能會非常納悶(連筆者剛開始學也是一樣)。

typeinterface 到底是差在哪裡啊?感覺都沒差啊!”

偷偷說一下,其實用法上感覺沒差多少,但意義上的差別可就超級大了啊

你可以同時使用 typeinterface,不過這樣交錯下來可能會導致剛入門 TypeScript 的人會有好像都可以亂用都沒差的錯覺。

因此筆者會把這個問題的探討慢慢呈現出來,今天先把 interface 這東西給讀者瀏覽過一次所有狀況。儘管本篇就算只有兩個大重點,但後面的探討只會越來越深入,開頭篇章先把基礎暖身做好再說~


上一篇
Day 11. 前線維護・特殊型別 X 無法無天 - Any & Unknown Type
下一篇
Day 13. 機動藍圖・介面的延展 X 功能與意義 - Interface Extension & Significance
系列文
讓 TypeScript 成為你全端開發的 ACE!51

尚未有邦友留言

立即登入留言