內容

移轉至 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 能夠更簡潔、更容易理解、更一致,並更能與 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

在您的 pubspec 中將 web 加入 dependencies

yaml
dependencies:
  web: ^0.5.0

以下區段涵蓋了一些從 dart:htmlpackage: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 名稱

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 程式碼移轉為使用 interop 方法,例如 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 類型頁面的 轉換 部分中瞭解如何使用 interop 轉換方法。

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 物件本身。請參閱模擬教學課程以取得更多資訊。

非原生 API

#

`dart:html` 類別也可能包含具有非平凡實作的 API。這些成員可能存在或不存在於 `package:web` 輔助程式中。如果你的程式碼依賴於該實作的具體內容,你可能會複製必要的程式碼。但是,如果你認為這不可行,或者該程式碼對其他使用者也有幫助,請考慮提交問題或上傳拉取請求至 package:web 以支援該成員。

區域

#

在 `dart:html` 中,回呼會自動分區。`package:web` 中並非如此。目前分區中沒有自動繫結回呼。

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

輔助工具

#

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

例如,核心 `package:web` 僅支援新增和移除事件監聽器。相反地,你可以使用串流輔助程式,它可以輕鬆地訂閱 Dart 串流的事件,而不用自己撰寫該程式碼。

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 的儲存庫中找到所有輔助程式及其文件。它們將持續更新,以協助使用者進行移轉,並讓使用網路 API 變得更容易。

範例

#

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