跳過到主要內容

遷移到 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:webdart: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:webdart: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

在 pubspec 中將 web 新增到 dependencies

dart pub add web

以下部分涵蓋了從 dart:html 遷移到 package:web 的一些常見問題。

對於任何其他遷移問題,請檢視 dart-lang/web 倉庫並提交問題。

重新命名

#

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 型別,請首先透過提交問題告知我們。

然後,您可以嘗試透過查詢現有 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輔助工具中,也可能不存在。如果您的程式碼依賴於該實現的具體細節,您或許可以複製必要的程式碼。但是,如果您認為這不可行,或者該程式碼對其他使用者也有益,請考慮提交問題或向 package:web 提交拉取請求以支援該成員。

Zone

#

dart:html 中,回撥是自動分割槽(zoned)的。但在 package:web 中並非如此。當前 Zone 中沒有回撥的自動繫結。

如果這對您的應用程式很重要,您仍然可以使用 Zone,但需要透過繫結回撥來自行編寫。有關更多詳細資訊,請參見 #54507。目前還沒有可自動執行此操作的轉換 API 或輔助工具

輔助工具

#

package:web 的核心包含 external 互操作成員,但沒有提供 dart:html 預設提供的其他功能。為了彌補這些差異,package:web 包含了輔助工具,以額外支援處理核心互操作中不直接可用的一些用例。輔助庫包含各種成員,以公開 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 的包示例