Flow 中的聯結類型使用詳解
在Flow 的使用入門一文中,我們在語法層面簡要介紹了 Flow 的實際使用;其中有一個聯結類型,本文介紹聯結類型的使用及相關知識點。
聯結類型簡介
「聯結類型」是筆者的翻譯,Flow 文檔中的原名是叫「Union Type」;意即多個類型聯結在一起。不過我也有看到譯文稱為「聯合類型」的;譯名暫時不重要,確保我們討論的是同一個東西就行。
聯結類型的書寫方式是多個類型用 | 符號聯結在一起,如 type A = 1 | 2 | 3;不過如果類型名稱較長,也可以分多行來寫,每一個類型一行,注意:每個類型名前都要有 | 符號。
// @flow type T = | string | number
聯結類型的使用
聯結類型可以將多個任意類型聯結在一起,比如:
// @flow type A = 1 | 2 | 3; type B = number | 'a' | boolean; type C = 'a' | 1 | true | Array<string>; type D = A | B | C;
如代碼所示,可以是多個字面量值類型的聯結,多個基本類型的聯結,甚至是多個聯結類型的再次聯結。從語義上聯結類型可以理解成「或」;如類型為 A 的變量值可以是 1 或者 2 或者 3;是一個允許的類型的集合。
既然提出了集合的概念,就有子集。在 Flow 的聯合類型中;如果某個聯結類型 T 滿足某一約束要求,那么類型 T 的子集構成的聯結類型,也滿足這一約束要求;可以說,針對兩個聯結類型 A 和 B,如果 A 是 B 的子集,那么 A 是 B 的子類型。
// @flow type Base = 1 | 2 | 3 | 4 | 5; type Sub = 1 | 2 | 3; function myMethod(arg: Base) { // ... code here } let a: Sub = 1; myMethod(a);
聯結類型的細化
函數參數接受一個聯結類型是很常用的;比如 jQuery:既可以接受一個 CSS 選擇符(string 類型),也可以接受一個函數(Function 類型);如果要給 jQuery 的參數寫一個類型定義,大概是這樣:
// @flow type T = string | Function; function jQuery(arg: T) { // ... } jQuery('div'); jQuery(function(){ // ... })
Flow 要求,如果一個函數接受一個聯結類型,傳入的參數類型可以是類型中的任意一個(one of those types);但是函數內部,必須對聯結類型的每一種情況進行處理(all of the possible types);在 Flow 的官網中,把這個原則稱為 requires one in, but all out ,而這個過程稱為 類型的細化(Refinements) 。
// @flow function myMethod(value: string | number | boolean){ if (typeof value === 'string') { // Block A } else if (typeof value === 'number') { // Block B } else { // Block C } }
在上述代碼中,定義 value 類型是 string、number 和 boolean 的聯結類型;因此在函數內部對 value 進行處理時,必須進行區分;只有 value 是 string 類型值才會運行到 A 代碼塊;只有 value 是 number 類型值才會運行到 B 代碼塊;而在 C 代碼塊中,已經把聯結類型中的 string 和 number 都排除了,所以這里可以安全得把 value 作為 boolean 類型處理。
這樣通過細化處理把聯結類型拆解逐一處理,可以確保聯結類型的安全使用。
不過有一點神奇的是:在處理聯結類型后,如果所有的可能類型都判斷并處理了,并且還有其他的邏輯分支,Flow 是不做檢查的。
// @flow function myMethod(value: string | number ) { if(typeof value === 'string') { // Block A; var a: string = value; } else if(typeof value === 'number') { // Block B; var b: number = value; } else { // Block C; var c: Object = value; var d: Array<*> = value.push('a'); var e: number = value - 1; } }
在代碼塊 C 中,對 value 進行的操作肯定是有沖突的;但是運行 Flow 卻不會報錯,因為 Flow 聰明的知道:在 A 和 B 代碼塊中,已經處理了 value 所有的可能性,程序運行是無法到達 C 代碼塊的。
Flow 檢查器必須知道每一個代碼塊的可到達性,以及運行到達這段代碼塊時變量的類型可能;這稱為靜態類型檢查器的可到達性分析( reachability analysis )。
互斥類型的細化
在之前的例子中,我們是通過 JavaScript 語言中的 typeof 來判斷變量到底是屬于聯結類型中的哪一個。但是在很多情況下,我們沒法使用 typeof 來進行;比如多個對象類型的聯結,那又如何處理呢?
// @flow type A = { name: string, } type B = { age: number, } type C = A | B;
類型 C 是 A 和 B 構成的聯結類型;A 定義的類型,即要求該類型的變量,必須有 name 屬性,取值是 string 類型(類似于 TypeScript 中的 Interface)。在 JavaScript 的代碼中,區分 A、B 兩種類型肯定能不能用 typeof,因為都是對象;針對這種類型,Flow 提供兩種方案。
一、互斥類型
// @flow type Success = { success: true, value: boolean, }; type Failure = { success: false, error: string } type Response = Success | Failure; function handleResponse(res: Response) { if(res.success){ var a: boolean = res.value; } else { var b: string = res.error; } }
類型 Success 和 Failure 都有一個 同名的屬性 (success),且定義的類型是一個 精確的字面量類型 ;Flow 將這樣的多個對象類型稱為互斥類型( disjoint unions )。他們之間就是根據這個同名的屬性進行區分的。
如果剛好你的業務數據中有可以用來區分的同名字段,可以使用互斥類型,否則還需要為了適應 Flow 的要求去修改業務數據結構。
二、確定結構的對象類型( exact object type )
使用對象類型,我們只能限制對象中必須有哪些屬性,這之外還有其他哪些屬性是不限制的;如果我們能精確的定義,接受的對象類型參數只能有哪些屬性,那就可以用 確定結構的對象類型 來做類型標注。 確定結構的對象類型 是在定義對象類型的時候,在大括號內部加上一對 | 符號,比如:
// @flow type T = {| name: string, age: number |}
這個類型的變量有且只有這兩個屬性。如果是兩個這樣的 確定結構的對象類型 構成的聯結類型,在使用時根據各自獨有的屬性就可以進行區分了。
理想的情況是這么用的:
// @flow type A = {| name: string |} type B = {| age: number |} type C = A | B; function myMethod(value: C) { if (typeof value.name === 'string') { var a: string = value.name; } else { var b: number = value.age; } } // 這個例子 Flow 檢查是會出錯的!!!
不過遺憾的是,Flow 當前(v0.42.0)對此的實現是不太完善的。
1. 從代碼邏輯來講,如果限制了參數 value 的類型必須是 C 的話,那么在 else 代碼塊里,可以確定 value 的類型只能是 B;但是 Flow 沒有推斷出來。如果你在使用中碰到這樣的坑,目前可以這么修改你的代碼使其通過 Flow 的檢查。
// @flow type A = {| name: string |} type B = {| age: number |} type C = A | B; function myMethod(value: C) { if (typeof value.name === 'string') { var a: string = value.name; } else if(typeof value.age === 'number') { var b: number = value.age; } }
2.
把上述代碼中的 typeof 判斷類型,改成用 in 操作符判斷屬性存在,符合 JavaScript 的語言邏輯,但是通不過 Flow 的檢查。
3. 如果直接用 if(o.p) 來區分,能通過 Flow 的檢查,但是從 JavaScript 語言邏輯來講是不正確的;比如判斷一個數字類型變量是否存在,數字為 0 時結果就不正確。
來自:https://zhuanlan.zhihu.com/p/26401539