深入探討 TypeScript 之 Type Assertion
C# 有所謂的 Object Initializer,讓我們可以很優雅的建立物件,並且將物件的 field 一次填滿,TypeScript 是否也提供如 C# 一樣的寫法呢?
Version
TypeScript 2.2
傳統建立物件方式
hero.ts
1 | export class Hero { |
使用 class 建立 model。
app.component.ts
1 | onAddHeroClick() { |
建立 Hero
物件,然後指定 field 值,最後再 push 進陣列。
這樣寫當然沒有錯,只是必須先透過 constructor 建立物件,然後一一指定 field 值,程式行數會很多,且沒那麼優雅。
C Sharp
1 | var hero = new Hero |
C# 的 Object Initializer 允許我們不用透過 constructor,直接在型別後面使用 {}
,且 Intellisense 會自動對 field 加以提示。
這種方式比起傳統物件導向寫法優雅。
TypeScript
在 2015 年,就有人開始討論如何在 TypeScript 提供 Object Initializer,也提出各種語法上的建議。
[New Feature] Initialize Classes by Using an Object Initializer#3895
TypeScript 有 3 種寫法,可以寫出類似 C# Object Initializer 風格的程式碼。
1 | const hero: Hero = { |
這種寫法也不用透過 new 與 constructor,語法精簡,且 Intellisense 會自動對 field 加以提示。
1 | const hero = <Hero>{ |
這種寫法也不用透過 new 與 constructor,使用 type assertion,類似泛型的寫法,將 object type 轉成 Hero
,語法精簡,且 Intellisense 會自動對 field 加以提示。
1 | const hero = { |
這種寫法也不用透過 new 與 constructor,是 type assertion 的另一種寫法,在最後補上 as
將 object type 轉成 Hero
,語法精簡,且 Intellisense 會自動對 field 加以提示。
<Foo>
vs. as Foo
<Foo>
與 as Foo
寫法都屬於 type assertion,該用哪一種寫法呢?
一開始 TypeScript 只提供 <Foo>
的語法,但這種寫法搭配 JSX 會有問題。
1 | var foo = <string>bar; |
因此 TypeScript 另外提供 as Foo
寫法給 JSX。
1 | var foo = bar as string; |
對於 Angular 來說,<Foo>
與 as Foo
都可以用,但就語意而言,<Foo>
寫法較優。
因為 type assertion 是在編譯時期的靜態轉型,而非執行時期的動態轉型,使用泛型的 <>
符號較能彰顯其編譯時期的特性。
Type Assertion vs. Type Casting
或許你會覺得 type assertion 就是一種轉型而已,只是使用了 <Foo>
或 as Foo
的語法,但事實上並不是如此。
在 TypeScript PlayGround,我們可以發現使用 type assertion 之後的 JavaScript 的差異:
TypeScript
1 | const hero = <Hero>{ |
JavaScript
1 | var hero = { |
也就是對於 JavaScript 而言,並沒有所謂的 Hero
型別,只是 object
型別,所以並沒有所謂的執行時期的動態轉型,type assertion 實際上只有兩個功能:
- 編譯時期的型別檢查
- 開發時期的 Intellisense。
Type Assertion 的盲點
Type assertion 並非萬靈丹,事實上它有以下盲點,回顧一下我們的 model:
hero.ts
1 | export class Hero { |
Hero 有兩個 fields。
使用 <Hero>
寫法,就算少寫了 state
,TypeScript language service 與 TSLint 都不會警告,且編譯後也沒有錯誤。
改用了 as Hero
寫法依舊,就算少寫了 state
,TypeScript language service 與 TSLint 都不會警告,且編譯後也沒有錯誤。
若使用了型別宣告,少寫了 state
,TypeScript language service 會提出警告,編譯也會失敗,明確告知少指定了 state
fields。
以上 3 種寫法,Intellisense 皆正常,但只有明確宣告型別,才能完整檢查出少了 field。
Nullable
若 model 有些 field 允許不指定值,卻又希望 TypeScript 強型別檢查呢?
1 | export class Hero { |
請在 field 名稱後方加上 ?
,則 TypeScript language service 不會提出警告,編譯也不會失敗。
Conclusion
- Type Assertion 並非最完美的強型別解決方案,只能對 Intellisense 有幫助。
- 若要完整的檢查,還是要明確的指定型別,如此才能發揮 TypeScript 的 type 威力。
Sample Code
完整的範例可以在我的 GitHub 上找到。
Reference
TypeScript, [New Feature] Initialize Classes by Using an Object Initializer#3895
TypeScript Deep Dive, Type Assertion