跳至主要內容

檔案簽章 (先類型)

Flow 透過依序處理每個檔案 (依賴順序) 來檢查程式碼庫。對於包含檢查程序中重要類型資訊的每個檔案,需要擷取簽章並儲存在主記憶體中,以供依賴它的檔案使用。Flow 仰賴檔案邊界的註解來建立這些簽章。我們將 Flow 架構的此項需求稱為「先類型」。

此架構的好處有兩個

  1. 它大幅提升了效能,特別是在重新檢查時。假設我們希望 Flow 檢查檔案 foo.js,而它尚未檢查其相依性。Flow 只透過查看匯出周圍的註解,就能萃取出相依性簽章。此程序主要是語法性的,因此比 Flow 舊版本(v0.125 之前)用於產生簽章的完整類型推論快很多。

  2. 它提升了錯誤的可靠性。推論的類型通常會變得複雜,並可能導致在下游檔案中回報錯誤,遠離其實際來源。檔案檔案邊界的類型註解有助於將此類錯誤定位,並在引入錯誤的檔案中解決它們。

此效能好處的折衷是,匯出的程式碼部分需要加上類型註解,或成為其類型可以輕易推論的表達式(例如數字和字串)。

可以在 這篇文章 中找到有關 Types-First 架構的更多資訊。

如何將您的程式碼庫升級至 Types-First

注意:Types-first 自 v0.134 起為預設模式,且自 v0.143 起為唯一可用模式。從那時起,不需要任何 .flowconfig 選項來啟用它。如果您要從較早的版本升級您的程式碼庫,這裡有一些有用的工具。

升級 Flow 版本

Types-first 模式在版本 0.125 中正式發布,但自版本 0.102 起已以實驗性狀態提供。如果您目前使用的是較舊的 Flow 版本,則必須先升級 Flow。使用最新的 Flow 版本是從上述效能好處中受益的最佳方式。

為 Types-First 準備您的程式碼庫

Types-first 需要在模組邊界加上註解,才能為檔案建置類型簽章。如果缺少這些註解,則會引發 signature-verification-failure,而程式碼相關部分的匯出類型將為 any

若要查看讓您的程式碼庫準備好 Types-first 所缺少的類型,請將下列程式碼行新增至 .flowconfig 檔案的 [options] 區段

well_formed_exports=true

例如,考慮匯出函式呼叫至 foo 的檔案 foo.js

declare function foo<T>(x: T): T;
module.exports = foo(1);

函式呼叫的回傳類型目前無法輕易推論(由於多型性、重載等功能)。其結果需要加上註解,因此您會看到以下錯誤

Cannot build a typed interface for this module. You should annotate the exports
of this module with types. Cannot determine the type of this call expression. Please
provide an annotation, e.g., by adding a type cast around this expression.
(`signature-verification-failure`)

4│ module.exports = foo(1);
^^^^^^

若要解決此問題,您可以新增註解,如下所示

declare function foo<T>(x: T): T;
module.exports = foo(1) as number;

注意:自版本 0.134 起,types-first 為預設模式。此模式會自動啟用 well_formed_exports,因此您會看到這些錯誤,而不會明確設定此旗標。建議在此升級部分設定 types_first=false

封存您的中間結果

當你在程式碼庫中加入類型時,你可以包含目錄,這樣當新的程式碼提交時,它們就不會退步,直到整個專案都具有良好的輸出。你可以透過在 .flowconfig 中加入下列行來執行此操作

well_formed_exports.includes=<PROJECT_ROOT>/path/to/directory

警告:這是子字串檢查,而不是正規表示式(出於效能考量)。

大型程式碼庫的 codemod

為大型程式碼庫加入必要的註解可能會很繁瑣。為了減輕這個負擔,我們提供了一個基於 Flow 推論的 codemod,可用於大量註解多個檔案。請參閱本教學課程以取得更多資訊。

啟用 types-first 標記

在你消除了簽章驗證錯誤後,你可以透過在 .flowconfig 檔案的 [options] 區段加入下列行來開啟 types-first 模式

types_first=true

你也可以將 --types-first 傳遞給 flow checkflow start 指令。

types_first 暗示了先前的 well_formed_exports 標記。一旦這個程序完成且 types-first 已啟用,你就可以移除 well_formed_exports

很遺憾地,無法為儲存庫的一部分啟用 types-first 模式;這個切換會影響目前 .flowconfig 管理的所有檔案。

注意:上述標記在 Flow 版本 >=0.102 中可用,並帶有 experimental. 前綴(在 v0.128 之前,它使用 whitelist 取代 includes

experimental.well_formed_exports=true
experimental.well_formed_exports.whitelist=<PROJECT_ROOT>/path/to/directory
experimental.types_first=true

注意:如果你使用的是預設啟用 types-first 的版本(例如 >=0.134),請確保在執行 codemod 時在 .flowconfig 中設定 types_first=false

處理新產生的錯誤

在經典模式和類型優先模式之間切換可能會導致一些新的 Flow 錯誤,除了我們前面提到的簽章驗證失敗之外。這些錯誤是基於註解的類型與其各自推斷的類型在解釋方式上的差異所致。

以下是常見的錯誤模式以及如何克服它們。

在匯出中將陣列元組視為常規陣列

在類型優先中,匯出位置中的陣列文字

module.exports = [e1, e2];

被視為具有類型 Array<t1 | t2>,其中 e1e2 的類型為 t1t2,而不是元組類型 [t1, t2]

在經典模式中,推斷的類型同時包含這兩種類型。這可能會導致匯入預期在匯入的第一個位置找到類型 t1 的檔案時發生錯誤。

修正:如果預期為元組類型,則需要在匯出方明確加入註解 [t1, t2]

匯出中的間接物件指定

Flow 允許程式碼

function foo(): void {}
foo.x = () => {};
foo.x.y = 2;
module.exports = foo;

但在類型優先中,匯出的類型將會是

{
(): void;
x: () => void;
}

換句話說,它不會考慮對 y 的更新。

修正:若要將對 y 的更新包含在匯出的類型中,則需要使用類型對匯出進行註解

{
(): void;
x: { (): void; y: number; };
};

對於更複雜的指定模式,例如

function foo(): void {}
Object.assign(foo, { x: 1});
module.exports = foo;

您需要手動使用 { (): void; x: number } 對匯出進行註解,或在函式定義之前進行指定

foo.x = 1;
function foo(): void {}
module.exports = foo;

請注意,在最後一個範例中,如果 Flow 類型優先在定義之後,它會擷取靜態更新

function foo(): void {}
foo.x = 1;
module.exports = foo;

具有更新的匯出變數

類型優先簽章萃取器不會擷取匯出的 let 繫結變數的後續更新。考慮範例

let foo: number | string = 1;
foo = "blah";
module.exports = foo;

在傳統模式中,匯出的類型會是 string。在 types-first 中,它會是 number | string,因此如果下游類型依賴於更精確的類型,那麼你可能會遇到一些錯誤。

修正:在更新和匯出中引入一個新的變數。例如

const foo1: number | string = 1;
const foo2 = "blah";
module.exports = foo2;