簡體   English   中英

在同構 Typescript 庫中引用特定於平台的類型的正確方法

[英]Right way to reference platform-specific types in isomorphic Typescript library

我正在嘗試編寫一個 Typescript 庫,我希望能夠在同時針對瀏覽器和節點時包含該庫。 我有兩個問題:在代碼主體中引用特定於平台的類型,以及在生成的.d.ts聲明中包含這些類型,這些聲明伴隨轉譯的 JS。

在第一種情況下,我想寫類似

  if (typeof window === "undefined") {
    // Do some Node-y fallback thing
  } else {
    // Do something with `window`
  }

如果我的lib編譯器選項中不包含"dom" (也就是說,如果我在tsconfig中只說lib: ["es2016"] ),則編譯失敗,因為未定義全局window (使用window只是lib.dom.d.ts之外的一個例子,它也可能是fetchResponseBlob等。)關鍵是通過檢查是否存在,代碼在運行時應該已經是安全的全局的object在使用前,是我想不通的類型。

在第二種情況下,我在構建庫后嘗試包含該庫時遇到錯誤。 我可以在lib選項中使用"dom"構建庫,生成的 output 包括類型,例如declare export function foo(x: string | Blob): void 問題是,如果消費代碼不包含Blob的定義(沒有"dom"庫),它就無法編譯,即使它實際上只是用string調用foo (或者根本不使用foo 。)。

如果我能提供幫助,我不希望我的庫(或消費者)嘗試使用偽造的windowBlob聲明來污染全局命名空間。 更多的等距庫已經出現,但我還沒有找到一個好的 Typescript 示例來遵循。 (如果這個主題對於 SO 來說太復雜了,我仍然非常感謝指向文檔或文章/博客文章的指針。)

我認為這是抽象的一個經典案例,也是一個簡單的案例。 也就是說,您針對IPlatform接口進行編碼並在代碼中引用該接口。 該接口反過來隱藏了所有平台特定的實現。

您還可以另外公開 API,以便消費者可以輕松地初始化“平台”,通常使用適當的“全局”object。 使用依賴注入將IPlatform的正確(特定於平台)實例注入到您的代碼中。 這應該會嚴重減少代碼中的分支並保持代碼更干凈。 正如您在問題中指出的那樣,您不必使用這種方法用虛假聲明污染您的代碼。

或者,您還可以從 package 中導出IPlatform實例,以便消費者也可以從中受益。

你提到的第二個問題:

問題是,如果消費代碼不包含 Blob 的定義(沒有“dom”庫),它就無法編譯,即使它實際上只是用字符串調用 foo(或者根本不使用 foo。)。

我認為這可以通過在消費者端安裝@types/node作為 devDependency 來輕松解決。 這應該是相對較小的足跡,因為這不會增加消費者的捆綁。

真正等距的模塊與環境無關。

您應該簡單地定義您打算從原語中使用的類型。 這樣,您的代碼將針對文字形狀,而不是環境。

這並不簡潔,但是,此方法的積極副作用是您的代碼永遠不會因為環境庫聲明文件的更新而中斷。

您可以使用文檔和示例來解釋如何在模塊中使用庫類型。

下面是一個例子:

TS游樂場

// Here, `value` is string, or an object type which is
// extended by an instance of `globalThis.Blob` in a browser:
function f1 (value: string | { text(): Promise<string> }): void {
  if (typeof value === 'string') {
    value.toUpperCase() // string
  }
  else  {
    value.text() // lib === 'DOM' ? Blob : any
  }
};

// Similarly, `process` is an object which is extended by `globalThis.process` from Node.js
function f2 (process: { env: Record<string, string | undefined> }): void {
  if (process.env.NODE_ENV === 'production') {
    // prod
  }
  else {
    // dev
  }
};

不幸的是,目前沒有很好的方法來做到這一點。

TypeScript 團隊成員建議的方法是利用 TypeScript 的接口合並功能,並在全局 scope 中的“前向聲明”接口,可以潛在地與相同接口的聲明合並。

一個常見的例子可能是 Node.js 中內置的 Buffer 類型。 在瀏覽器和 Node.js 上下文中運行的庫可以在 state 中處理一個 Buffer(如果給定一個),但 Buffer 的功能對聲明並不重要。

 export declare function printStuff(str: string): void; /** * NOTE: Only works in Node.js */ export declare function printStuff(buff: Buffer): void;

解決此問題的一種技術是在全局 scope 中使用空接口“前向聲明”緩沖區,以后可以合並。

 declare global { interface Buffer {} } export declare function printStuff(str: string): void; /** * NOTE: Only works in Node.js */ export declare function printStuff(buff: Buffer): void;

這種方法存在一些問題,如上面鏈接的問題中所述:

  • 它僅適用於類和接口,不適用於聯合類型等其他類型。
  • 接口合並並不總是能正確解決兩個接口中聲明之間的沖突。

TypeScript 團隊提出了一個名為“占位符類型聲明”的新功能來解決這些問題,但它目前似乎不在路線圖中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM