跳至主要內容

遷移至 package:web

Dart 的 package:web 公開瀏覽器 API 的存取權,啟用 Dart 應用程式和 Web 之間的互通性。使用 package:web 與瀏覽器互動,並操作 DOM 中的物件和元素。

dart
import 'package:web/web.dart';

void main() {
  final div = document.querySelector('div')!;
  div.text = 'Text set at ${DateTime.now()}';
}

package:web vs dart:html

#

package:web 的目標是透過解決現有 Dart Web 函式庫的幾個問題,來徹底改造 Dart 公開 Web API 的方式

  1. Wasm 相容性

    套件只有在使用 dart:js_interopdart:js_interop_unsafe 的情況下才能與 Wasm 相容。package:web 基於 dart:js_interop,因此預設情況下,它在 dart2wasm 上受到支援。

    Dart 核心 Web 函式庫,例如 dart:htmldart:svg,已被棄用,並且在編譯為 Wasm 時不受支援

  2. 保持現代化

    package:web 使用 Web IDL 來自動產生 互通成員互通類型,用於 IDL 中的每個宣告。直接產生參考,而不是 dart:html 中的額外成員和抽象概念,使 package:web 更簡潔、更容易理解、更一致,並且更能跟上 Web 開發的未來發展。

  3. 版本控制

    由於它是一個套件,package:web 比像 dart:html 這樣的函式庫更容易進行版本控制,並避免在演進時破壞使用者程式碼。它也使程式碼更不具排他性,並更開放接受貢獻。開發人員可以建立他們自己的 替代互通宣告,並將它們與 package:web 一起使用,而不會發生衝突。


這些改進自然而然地導致 package:webdart:html 之間存在一些實作差異。影響現有套件最大的變更,例如 IDL 重新命名類型測試,將在後續的遷移章節中說明。雖然為了簡潔起見,我們僅提及 dart:html,但相同的遷移模式適用於任何其他 Dart 核心 Web 函式庫,例如 dart:svg

dart:html 遷移

#

移除 dart:html 匯入,並將其替換為 package:web/web.dart

dart
import 'dart:html' as html; // Remove
import 'package:web/web.dart' as web; // Add

web 新增至您的 pubspec 中的 dependencies

dart pub add web

以下章節涵蓋從 dart:htmlpackage:web 的一些常見遷移問題。

對於任何其他遷移問題,請查看 dart-lang/web 儲存庫並提交 issue。

重新命名

#

dart:html 中的許多符號已從其原始 IDL 宣告中重新命名,以更符合 Dart 風格。例如,appendChild 變為 appendHTMLElement 變為 HtmlElement 等。

相比之下,為了減少混淆,package:web 使用來自 IDL 定義的原始名稱。dart fix 可用於轉換在 dart:htmlpackage:web 之間重新命名的類型。

變更匯入後,任何重新命名的物件都會成為新的「未定義」錯誤。您可以透過以下任一方式解決這些問題

  • 從 CLI,執行 dart fix --dry-run
  • 在您的 IDE 中,選取 dart fix重新命名為 'package:web name'

dart fix 涵蓋了許多常見的類型重新命名。如果您遇到沒有 dart fix 可以重新命名的 dart:html 類型,請先透過提交 issue 來告知我們。

然後,您可以嘗試透過查閱其定義來手動發現現有 dart:html 成員的 package:web 類型名稱。dart:html 成員定義上的 @Native 註解值會告知編譯器將該類型的任何 JS 物件視為它註解的 Dart 類別。例如,@Native 註解告訴我們 dart:htmlHtmlElement 成員的原生 JS 名稱是 HTMLElement,因此 package:web 名稱也將是 HTMLElement

dart
@Native("HTMLElement")
class HtmlElement extends Element implements NoncedElement { }

若要尋找 package:web 中未定義成員的 dart:html 定義,請嘗試以下任一方法

  • 在 IDE 中按住 Ctrl 或 Command 並點擊未定義的名稱,然後選擇前往定義
  • dart:html API 文件中搜尋該名稱,並在其頁面的註解下查看。

同樣地,您可能會找到未定義的 package:web API,其對應的 dart:html 成員的定義使用了關鍵字 native。檢查定義是否使用了 @JSName 註解來進行重新命名;註解的值會告訴您成員在 package:web 中使用的名稱

dart
@JSName('appendChild')
Node append(Node node) native;

native 是一個內部關鍵字,在此上下文中與 external 的意思相同。

類型測試

#

使用 dart:html 的程式碼通常會利用執行階段檢查,例如 is。當與 dart:html 物件一起使用時,isas 會驗證物件是否為 @Native 註解中的 JS 類型。相比之下,所有 package:web 類型都會被具體化為 JSObject。這表示執行階段類型測試將導致 dart:htmlpackage:web 類型之間產生不同的行為。

為了能夠執行類型測試,請遷移任何使用 is 類型測試的 dart:html 程式碼,以使用 互通方法,例如 instanceOfString,或更方便且類型化的 isA 輔助函式 (從 Dart 3.4 開始提供)。JS 類型頁面的相容性、類型檢查和轉換章節詳細介紹了替代方案。

dart
obj is Window; // Remove
obj.instanceOfString('Window'); // Add

類型簽章

#

dart:html 中的許多 API 在其類型簽章中支援各種 Dart 類型。由於 dart:js_interop 限制可以寫入的類型,因此 package:web 中的某些成員現在會要求您在呼叫成員之前轉換值。從 JS 類型頁面的 轉換 章節了解如何使用互通轉換方法。

dart
window.addEventListener('click', callback); // Remove
window.addEventListener('click', callback.toJS); // Add

一般來說,您可以發現哪些方法需要轉換,因為它們會被標記為某種變體的例外

A value of type '...' can't be assigned to a variable of type 'JSFunction?'

條件式匯入

#

程式碼通常會根據是否支援 dart:html 來使用條件式匯入,以區分原生和 Web

dart
export 'src/hw_none.dart'
    if (dart.library.io) 'src/hw_io.dart'
    if (dart.library.html) 'src/hw_html.dart';

但是,由於 dart:html 已被棄用,並且在編譯為 Wasm 時不受支援,因此現在正確的替代方案是使用 dart.library.js_interop 來區分原生和 Web

dart
export 'src/hw_none.dart' // Stub implementation
    if (dart.library.io) 'src/hw_io.dart' // dart:io implementation
    if (dart.library.js_interop) 'src/hw_web.dart'; // package:web implementation

虛擬調度和模擬

#

dart:html 類別支援虛擬調度,但由於 JS 互通使用擴充類型,因此 不可能進行虛擬調度。同樣地,使用 package:web 類型的 dynamic 呼叫將無法如預期般運作 (或者,它們可能只是碰巧繼續運作,但在移除 dart:html 後將停止運作),因為它們的成員僅在靜態時可用。遷移所有依賴虛擬調度的程式碼以避免此問題。

虛擬調度的一個用例是模擬。如果您有一個模擬類別 implements dart:html 類別,則無法使用它來實作 package:web 類型。相反地,最好模擬 JS 物件本身。請參閱模擬教學以取得更多資訊。

native API

#

dart:html 類別也可能包含具有非簡單實作的 API。這些成員可能存在也可能不存在於 package:web 輔助函式中。如果您的程式碼依賴於該實作的細節,您或許可以複製必要的程式碼。但是,如果您認為這不可行,或者如果該程式碼對其他使用者也有益,請考慮提交 issue 或上傳 pull request 到 package:web 以支援該成員。

Zones

#

dart:html 中,回呼會自動 zoned。在 package:web 中情況並非如此。目前區域中沒有回呼的自動繫結。

如果這對您的應用程式很重要,您仍然可以使用 zones,但您必須透過繫結回呼來自行撰寫。請參閱 #54507 以取得更多詳細資訊。目前還沒有可用的轉換 API 或 輔助函式 來自動執行此操作。

Helpers

#

package:web 的核心包含 external 互通成員,但不提供 dart:html 預設提供的其他功能。為了減輕這些差異,package:web 包含 helpers,以提供額外支援,處理許多無法直接透過核心互通取得的用例。輔助函式庫包含各種成員,以公開來自 Dart Web 函式庫的一些舊版功能。

例如,核心 package:web 僅支援新增和移除事件監聽器。相反地,您可以使用 stream 輔助函式,讓您可以使用 Dart Stream 輕鬆訂閱事件,而無需自行撰寫該程式碼。

dart
// Original dart:html version:
final htmlInput = InputElement();
await htmlInput.onBlur.first;

// Migrated package:web version:
final webInput = HTMLInputElement();
await webInput.onBlur.first;

您可以在儲存庫 package:web/helpers 中找到所有輔助函式及其文件。它們將持續更新,以協助使用者遷移,並使其更容易使用 Web API。

範例

#

以下是一些已從 dart:html 遷移到 package:web 的套件範例