遷移至 package:web
Dart 的 package:web
公開瀏覽器 API 的存取權,啟用 Dart 應用程式和 Web 之間的互通性。使用 package:web
與瀏覽器互動,並操作 DOM 中的物件和元素。
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 的方式
Wasm 相容性
套件只有在使用
dart:js_interop
和dart:js_interop_unsafe
的情況下才能與 Wasm 相容。package:web
基於dart:js_interop
,因此預設情況下,它在dart2wasm
上受到支援。Dart 核心 Web 函式庫,例如
dart:html
和dart:svg
,已被棄用,並且在編譯為 Wasm 時不受支援。保持現代化
package:web
使用 Web IDL 來自動產生 互通成員 和 互通類型,用於 IDL 中的每個宣告。直接產生參考,而不是dart:html
中的額外成員和抽象概念,使package:web
更簡潔、更容易理解、更一致,並且更能跟上 Web 開發的未來發展。版本控制
由於它是一個套件,
package:web
比像dart:html
這樣的函式庫更容易進行版本控制,並避免在演進時破壞使用者程式碼。它也使程式碼更不具排他性,並更開放接受貢獻。開發人員可以建立他們自己的 替代互通宣告,並將它們與package:web
一起使用,而不會發生衝突。
這些改進自然而然地導致 package:web
和 dart:html
之間存在一些實作差異。影響現有套件最大的變更,例如 IDL 重新命名 和 類型測試,將在後續的遷移章節中說明。雖然為了簡潔起見,我們僅提及 dart:html
,但相同的遷移模式適用於任何其他 Dart 核心 Web 函式庫,例如 dart:svg
。
從 dart:html
遷移
#移除 dart:html
匯入,並將其替換為 package:web/web.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 儲存庫並提交 issue。
重新命名
#dart:html
中的許多符號已從其原始 IDL 宣告中重新命名,以更符合 Dart 風格。例如,appendChild
變為 append
,HTMLElement
變為 HtmlElement
等。
相比之下,為了減少混淆,package:web
使用來自 IDL 定義的原始名稱。dart fix
可用於轉換在 dart:html
和 package: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:html
的 HtmlElement
成員的原生 JS 名稱是 HTMLElement
,因此 package:web
名稱也將是 HTMLElement
@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
中使用的名稱
@JSName('appendChild')
Node append(Node node) native;
native
是一個內部關鍵字,在此上下文中與 external
的意思相同。
類型測試
#使用 dart:html
的程式碼通常會利用執行階段檢查,例如 is
。當與 dart:html
物件一起使用時,is
和 as
會驗證物件是否為 @Native
註解中的 JS 類型。相比之下,所有 package:web
類型都會被具體化為 JSObject
。這表示執行階段類型測試將導致 dart:html
和 package:web
類型之間產生不同的行為。
為了能夠執行類型測試,請遷移任何使用 is
類型測試的 dart:html
程式碼,以使用 互通方法,例如 instanceOfString
,或更方便且類型化的 isA
輔助函式 (從 Dart 3.4 開始提供)。JS 類型頁面的相容性、類型檢查和轉換章節詳細介紹了替代方案。
obj is Window; // Remove
obj.instanceOfString('Window'); // Add
類型簽章
#dart:html
中的許多 API 在其類型簽章中支援各種 Dart 類型。由於 dart:js_interop
限制可以寫入的類型,因此 package:web
中的某些成員現在會要求您在呼叫成員之前轉換值。從 JS 類型頁面的 轉換 章節了解如何使用互通轉換方法。
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
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
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
輕鬆訂閱事件,而無需自行撰寫該程式碼。
// 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
的套件範例
除非另有說明,否則本網站上的文件反映 Dart 3.7.1 版本。頁面上次更新於 2025-01-31。 檢視原始碼 或 回報問題。