迁移到 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 与 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在 pubspec 中将 web 添加到 dependencies
dart pub add web以下部分涵盖了从 dart:html 迁移到 package:web 的一些常见问题。
对于任何其他迁移问题,请查看 dart-lang/web 仓库并提交问题。
重命名
#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 类型,请首先通过提交问题告知我们。
然后,您可以尝试通过查找现有 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:htmlAPI 文档中搜索该名称,并在注解下查看其页面。
同样,您可能会发现一个未定义的 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 的辅助工具中,也可能不存在。如果您的代码依赖于该实现的具体细节,您或许可以复制必要的代码。但是,如果您认为这不可行,或者该代码对其他用户也有益,请考虑提交问题或向 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 订阅事件。
// 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 的包示例