型別扮演表達式 ( Type Casting Expressions )
中文名稱我自己寫寫的~ 可能不是正規的表達,但是我個人認為是有助於理解的
常常我們在做型別確認的時候,會要 寫一個
function
或是宣告變數來確認// @flow
function fn(num: number) {
// ...
}
fn(123);
fn('123'); // 錯誤!
// 或是
const num: number = 123;
const num2: string = 123; // 錯誤!
用久了確實是有點麻煩。這還只是基礎型別呢!如果哪天要使用自定義的型別那真是惡夢阿~
我們的痛苦 Flow 是知道的, Flow 支援 型別扮演表達式 ( Type Casting Expressions ) 來解決這個問題。
Type Casting Expression
的語法是在小括號內 ( )
使用 value
加上冒號 :
在接上 Type
完成一個表達式。(value: Type)
小括號是必要的哦!避免跟其他表達式混淆。
它可以應用在各種場景:
// 這裡只是表達式的範例
// 實際寫上去是會報錯的哦~ 因為我們並沒有 Type, value, prop 等變數的定義
let val = (value: Type);
let obj = { prop: (value: Type) };
let arr = ([(value: Type), (value: Type)]: Array<Type>);
在表達式中,還可以放入其他邏輯表達式。
(2 + 2: number)
當我們用
babel
編譯過後,它只會留下 value
的部分而已。// @flow
(value: Type)
value
( 這段雖然 IDE 會報錯,但是
babel
還是可以轉的哦,自己試試看吧! )其實這個
Type Casting Expressions
的中文名我也想了很久,到底是要用型別扮演,還是轉換 會比較好 ( 開始後悔年輕沒有好好上體育老師的國文課了... )因為以下的語法是通的哦
// @flow
let value = 42;
(value: 42); // ok!
(value: number); // ok!
(value: 43); // 錯誤!
第 4 行我們問他說,
value
可不可以是 42
他說可以。
第 5 行我們問他說,value
可不可以是 number
他也說可以。
第 6 行我們問他說,value
可不可以是 43
他說不行。所以我的解讀是,它作為扮演來解釋會比較精準一點。
當我們使用
Type Casting Expression
回傳時,在 Flow 中它會回傳它的型態,什麼意思呢?看下面的範例吧
let value = 42;
(value: 42); // ok
(value: number); // ok
let newValue = (value: number);
(newValue: 42); // 錯誤!
(newValue: number); // ok
以我們直覺上來說,
newValue
等於 value
,所以當我們問它 (newValue: 42)
的時候應該要說"是"才對 ( 實際上他也 是 42
沒錯 ),但是 Flow 硬生生的就給它報錯了。
為什麼呢?因為在 Flow 中 Type Casting Expression
的回傳的型別資訊是冒號後面的型別,而不是值得型別 ( 儘管實際上在 JavaScript 當中是值 ),所以在範例中,他只知道 newValue
是 number
而不知道它是 42
。在剛才的演示中,我們有發現
Type Casting
這個動作只能在扮演相同的型別,是不可以扮演成不同的型別的。但是有一個型別它可以扮演所有型別,那就是 any,在很多情況下我們會避免使用 any,畢竟使用了 any,就等於失去了型別判斷的意義,容易造成無法預期的錯誤。
一樣拿官方的範例來做為演示
let value = 42;
(value: number);// ok
(value: string); // 錯誤
let newValue = ((value: any) string);
(newValue: number); // 錯誤
(newValue: string); // ok
在一開始,我們問說
(value: string)
的時候 Flow 跟我們說不可以,他不是 string
後來我們先問說 (value: any)
,Flow 說可以,他是 any
,現在 Flow 已經把 (value: any)
當成 any 來看待了,
我們後面又接著問 ((value: any): string)
,Flow 說可以,any
可以是 string
。因此我們在最後兩行
(newValue: number)
的時候錯了,反倒是 (newValue: string)
的時候正確。可是實際上的 newValue
值卻是數字 42
,而非字串。當然,這樣的轉換型別是不建議的,用得不好就像上例這樣,Flow 都被自己搞到給出錯誤的提示,但是如果
any
型別使用得當,在某些真的需要使用型別轉換的場景則又顯得格外的實用。官方給了我們一個非常實用的用例。
用例場景是當我們要 clone 物件的時候,我們可能會先寫出像這樣的一段
// @flow
function cloneObject(obj) {
const clone = {};
Object.keys(obj).forEach((key) => {
clone[key] = obj[key]
})
return clone;
}
// --- 測試 ---
type Person = {
name: string,
age: number,
};
const person001: Person = {
name: 'Wayne',
age: 26,
};
(cloneObject(person001): Object); // ok
(cloneObject(person001): Person); // 錯誤
這是很簡單的 clone 物件 function,他可以回傳一個
Object
型態,但是卻沒有辦法做到回傳收到的 Person
型態。
( Object
型態源自於 const clone = {};
的預設型別 )如果我們想要讓它能如預期回傳收到的
Person
型態就可以用到剛才的 any
型態了。// @flow
function cloneObject(obj) {
const clone = {};
Object.keys(obj).forEach((key) => {
clone[key] = obj[key]
})
return ((clone: any): typeof obj); // <<
}
// --- 測試 ---
type Person = {
name: string,
age: number,
};
const person001: Person = {
name: 'Wayne',
age: 26,
};
(cloneObject(person001): Object); // ok
(cloneObject(person001): Person); // ok
我們只有動到
return
那行,先將 clone
物件轉換為 any
型態,現在它可以扮演任何型別了,再用 typeof
取出 obj
的型別,將 clone
轉過去,就大功告成了!還不錯吧?