前兩天介紹了如何建立 TS 專案的運行環境,接下來的幾天要近一步深入探討 TS 的型別系統了。在討論之前,讓我們先來回顧一下昨天的學習,試著回答看看:
- 要透過哪個檔案進行客製化的編譯設定呢?
- compilerOptions、files、include 和 exclude屬性分別是做什麼用的?
如果不清楚答案的話,可以看看 Day03 的文章喔!
還記得第一天介紹什麼是 TS 時,有提到 TS 有點類似「擴充板的 JS 」,簡單來說,TS 就像是帶有型別系統的 JS,可以編譯成不同版本的 JS。當然,TS 和 JS 的差異不僅僅如此,但現在我們只專注討論「型別系統」這部分。
型別系統有許多優點,最重要的便是能夠在撰寫程式碼時考量靜態型別,當型別有誤就會立刻報錯,讓開發者在開發階段就能避免掉許多無預期的bugs。
以上面的程式碼為例,宣告一個變數 a ,值為字串 123,而後重新賦值為 5,但這時候,TS 判斷變數 a 的型別是字串,不能賦值數字 5,兩者型別不一致,此時,就會報錯(a 下方會有紅色底線~
)。
等等,那 TS 怎麼判斷變數的資料型別呢?
事實上,在 TS 中有三種方式讓編譯器解析判斷資料型別,分別是型別推論(Type Inference)、型別斷言(Type Assertion)和型別註記(Type Annotation)。
在 TS 中,倘若沒有明確註記資料型別,聰明的 TS 編譯器便會自動推論出資料型別啦。如
下方的程式碼,宣告一個變數 a ,值為'123'
這時候,將滑鼠停在變數 a 上,此時,編輯器已經告訴我它是字串型別,但其實,我們完全沒有提到變數 a 的資料類型,變數a是字串型別
是 TS 編譯器自動從程式碼中推論出來的,推論出型別後,當我們嘗試以非字串的資料賦值時就會報錯。
事實上,開發者比 TS 更了解編寫的程式碼。因此,TS 允許開發者覆蓋它的推論,這樣的機制稱為「型別斷言」。編譯器會接受開發者手動寫下的斷言,並且不會再送出警告錯誤。
型別斷言有點類似其他強語言如Java中的型別轉換(Type casting),但在TS中它只是編譯階段的功能而已。
型別斷言有兩種寫法:
第一種是<型別>值
(angle-bracket <>)寫法
let code: any = 123;
let employeeCode = <number> code;
第二種則是值 as 型別
(as keyword)寫法
let code: any = 123;
let employeeCode = code as number;
註:兩者寫法效果相同,只是若是開發 React 專案使用 JSX 語法時只能用第二種。
透過TypeScript Deep Dive和TypeScript 入門教程的範例,來看看型別斷言的使用情境吧!
情境1. JS 程式碼轉換成 TS 程式碼
const foo = {}
foo.bar = 123 // Error: property 'bar' does not exist on `{}`
foo.bas = 'hello' // Error: property 'bas' does not exist on `{}`
我們將上方的 JS 程式碼放進 TS 中會出現錯誤,因為 foo 的型別推論是沒有任何屬性的物件,因此,不能在屬性上添加 bar 或 bas ,這時候透過型別斷言可以避免這個問題
interface Foo {
bar: number;
bas: string;
}
const foo = {} as Foo;
或是也可以寫成
const foo = <Foo> {}
foo.bar = 123; //OK
foo.bas = 'hello'; //OK
這時候,添加 bar 和 bas 屬性就不會報錯了。
情境2. 將聯合型別的變數指定為更加具體的型別
TS 有一種型別為聯合型別(細節後面的文章會再介紹),表示值可以是多種型別的一種。下方的程式碼表示 getLength 這個函式的參數可能為字串或數字的其中一種,然而 length 不是字串和數字的的共同属性,所以會報錯。
function getLength(something: string | number): number {
if (something.length) {
return something.length;
} else {
return something.toString().length;
}
}
// error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
這時候就可以使用「型別斷言」將 something 斷言為字串(string)
function getLength(something: string | number): number {
if ((<string>something).length) {
return (<string>something).length;
} else {
return something.toString().length;
}
}
如此,something.length 就不會報錯了!
值得注意的是,TS 的型別斷言並非型別轉換(Type casting),雖然上面有講到,型別斷言有點類似其他強語言如Java中的型別轉換,但實際上仍是不同的。斷言聯合型別中不存在的型別是不行的。
function toBoolean(something: string | number): boolean {
return <boolean>something;
}
// index.ts(2,10): error TS2352: Type 'string | number' cannot be converted to type 'boolean'.
// Type 'number' is not comparable to type 'boolean'.
TS 讓開發者可以透過手動註解的方式,明確宣告資料型別,告訴編譯器必須符合註解的型別,以方便在開發時就抓到變數的錯誤賦值問題。
在做型別註解時,會在變數、參數或屬性後面加上冒號:型別
,冒號後方可加一個空格,例如:
//變數的型別註解
const age: number = 32
//函式參數的型別註解
function display(name: string){
console.log(name);
}
//函式參數/回傳值的型別註解
function display(a: number,b: number): number{
return a + b
}
宣告型別之後,就不能使用其他型別的資料進行賦值,否則會報錯。
補充:型別註解 v.s 型別斷言的比較
在研究型別註解和斷言的時候,對於兩者的具體使用情境其實有點模糊。在和工作室的大神請教後,整理我所理解兩者的使用差異如下:
- 大部分情況下會使用型別註解 ; 型別斷言使用情境較少。
型別註解
告訴編譯器這個資料必須永遠都是這個型別 ; 而型別斷言
則主要用在覆蓋 TS 編譯器自動進行的型別推斷和型別相容性規則(Type Compatibility),告訴編譯器你知道這個值要符合斷言的型別,從而避免了上面範例中型別不兼容的錯誤產生。關於兼容性的討論在stackoverflow看到不錯的討論。- 型別註解大部分使用在初始化階段,像是宣告變數、函式參數或回傳值型別等 ; 而型別斷言可能用在接收外部參數,中間過程需要明確指定資料型別的時候。
今天初步介紹了 TS 的型別系統、TS 判別資料型別的三種方式 - 型別推論(Type Inference)、型別斷言(Type Assertion)和型別註解(Type Annotation)以及撰寫語法。
簡單來說,型別推論
就是在没有明確指定型別的情況下,由 TS 自動判斷型別 ; 型別斷言
則是手動指定資料型別,可以覆蓋掉 TS 自動推論的資料型別 ; 型別註解
則是明確指定資料型別,三者的使用情境不太相同,各種使用情境後面的文章再來細細探討。
明天終於可以來介紹 TS 各種資料型別了!Ya ~
如有錯誤的地方,還請留言告知,我會盡快修改調整,感謝:)
參考資料:
TypeScript Deep Drive
TypeScript 官網
Poetry Blog
TypeScript入門教程
TypeScript cheatsheat