目錄

遷移至 package:web

Dart 的 package:web 提供對瀏覽器 API 的存取,實現 Dart 應用程式與網頁之間的互通性。使用 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 網頁函式庫的一些問題,來改造 Dart 公開網頁 API 的方式

  1. Wasm 相容性

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

    Dart 核心網頁函式庫,例如 dart:htmldart:svg,在編譯為 Wasm 時不受支援

  2. 保持現代化

    package:web 使用 Web IDL 來自動產生 IDL 中每個宣告的互通成員互通型別。直接產生參考,而不是像 dart:html 中那樣加入額外的成員和抽象,使 package:web 更簡潔、更容易理解、更一致,並且更能與未來網頁的發展保持同步。

  3. 版本控制

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


這些改進自然會導致 package:webdart:html 之間的一些實作差異。在後續的遷移章節中,將說明對現有套件影響最大的變更,例如 IDL 重新命名型別測試。為了簡潔起見,我們僅提及 dart:html,但相同的遷移模式適用於任何其他 Dart 核心網頁函式庫,例如 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: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 來使用條件式匯入,以區分原生和網頁

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

但是,由於在編譯為 Wasm 時不支援 dart:html,因此現在正確的替代方法是使用 dart.library.js_interop 來區分原生和網頁

dart
export 'src/hw_none.dart'
    if (dart.library.io) 'src/hw_io.dart'
    if (dart.library.js_interop) 'src/hw_web.dart';

虛擬分派與模擬

#

dart:html 類別支援虛擬分派,但是由於 JS 互通使用擴展型別,因此無法使用虛擬分派。同樣地,使用 package:web 型別的 dynamic 呼叫將無法如預期運作(或者,它們可能會繼續運作只是偶然,但當移除 dart:html 時就會停止),因為它們的成員僅在靜態情況下可用。遷移所有依賴虛擬分派的程式碼,以避免此問題。

虛擬調用的其中一個用途是模擬。如果您有一個模擬類別 implements 一個 dart:html 類別,它就不能用來實作 package:web 類型。相反地,請優先模擬 JS 物件本身。詳情請參閱模擬教學

native API

#

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

區域

#

dart:html 中,回呼會自動進行分區。package:web 則不然。目前的區域中沒有回呼的自動綁定。

如果這對您的應用程式很重要,您仍然可以使用區域,但您必須透過綁定回呼自行撰寫。詳情請參閱 #54507。目前還沒有轉換 API 或 helper 可用於自動執行此操作。

輔助程式

#

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

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

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

// package:web version
HTMLInputElement webInput = document.createElement('input') as HTMLInputElement;
await webInput.onBlur.first;

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

範例

#

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