目录

JS 类型

Dart 值和 JS 值属于不同的语言域。编译到 Wasm 时,它们也在不同的 运行时 执行。因此,您应将 JS 值视为外部类型。为了为 JS 值提供 Dart 类型,dart:js_interop公开了一组以 JS 为前缀的类型,称为“JS 类型”。这些类型用于在编译时区分 Dart 值和 JS 值。

重要的是,这些类型的具体化方式取决于您是编译到 Wasm 还是 JS。这意味着它们的运行时类型会有所不同,因此您 不能使用 is 检查和 as 强制转换 。为了与这些 JS 值交互并检查它们,您应该使用external互操作成员或 转换

类型层次结构

#

JS 类型形成了一个自然的类型层次结构:

  • 最顶层类型: JSAny ,它是任何非 nullish 的 JS 值
    • 原语类型: JSNumberJSBooleanJSString
    • JSSymbol
    • JSBigInt
    • JSObject ,它是任何 JS 对象
      • JSFunction
        • JSExportedDartFunction ,它表示已转换为 JS 函数的 Dart 回调函数
      • JSArray
      • JSPromise
      • JSDataView
      • JSTypedArray
        • JS 类型化数组,例如 JSUint8Array
      • JSBoxedDartObject ,允许用户在同一 Dart 运行时中不透明地打包和传递 Dart 值
        • 从 Dart 3.4 开始, dart:js_interop 中的 ExternalDartReference 类型也允许用户不透明地传递 Dart 值,但它不是 JS 类型。在此处[了解]每个选项之间的权衡 here

您可以在[dart:js_interop API 文档]中找到每种类型的定义。

转换

#

要将值从一个域用于另一个域,您可能需要将值 转换 为另一个域的相应类型。例如,您可能希望将 Dart List<JSString> 转换为 JS 字符串数组(由 JS 类型 JSArray<JSString> 表示),以便您可以将数组传递给 JS 互操作 API。

Dart 在各种 Dart 类型和 JS 类型上提供许多转换成员,以便为您转换域之间的值。

将值从 Dart 转换为 JS 的成员通常以 toJS 开头:

dart
String str = 'hello world';
JSString jsStr = str.toJS;

将值从 JS 转换为 Dart 的成员通常以 toDart 开头:

dart
JSNumber jsNum = ...;
int integer = jsNum.toDartInt;

并非所有 JS 类型都有转换,并非所有 Dart 类型都有转换。通常,转换表如下所示:

dart:js_interop 类型Dart 类型
JSNumberJSBooleanJSStringnumintdoubleboolString
JSExportedDartFunctionFunction
JSArray<T extends JSAny?>List<T extends JSAny?>
JSPromise<T extends JSAny?>Future<T extends JSAny?>
JSUint8Array 这样的类型化数组来自 dart:typed_data 的类型化列表
JSBoxedDartObject不透明的 Dart 值
ExternalDartReference不透明的 Dart 值

external 声明和 Function.toJS 的要求

#

为了确保类型安全性和一致性,编译器对可以流入和流出 JS 的类型施加了要求。不允许将任意 Dart 值传递到 JS。相反,编译器要求用户使用兼容的互操作类型 ExternalDartReference 或原语类型,然后编译器会隐式转换这些类型。例如,这些是允许的:

gooddart
@JS()
external void primitives(String a, int b, double c, num d, bool e);
gooddart
@JS()
external JSArray jsTypes(JSObject _, JSString __);
gooddart
extension type InteropType(JSObject _) implements JSObject {}

@JS()
external InteropType get interopType;
gooddart
@JS()
external void externalDartReference(ExternalDartReference _);

而这些会返回错误:

baddart
@JS()
external Function get function;
baddart
@JS()
external set list(List _);

当您使用Function.toJS 使 Dart 函数可在 JS 中调用时,也会存在相同的这些要求。流入和流出此回调的值必须是兼容的互操作类型或原语类型。

如果您使用像 String 这样的 Dart 原语,则编译器中会发生隐式转换,以将该值从 JS 值转换为 Dart 值。如果性能至关重要,并且您不需要检查字符串的内容,那么使用 JSString 来避免转换成本可能更有意义,例如在第二个示例中。

兼容性、类型检查和强制转换

#

JS 类型的运行时类型可能因编译器而异。这会影响运行时类型检查和强制转换。因此,几乎总是避免 is 检查,其中值是互操作类型或目标类型是互操作类型:

baddart
void f(JSAny a) {
  if (a is String) { … }
}
baddart
void f(JSAny a) {
  if (a is JSObject) { … }
}

此外,避免 Dart 类型和互操作类型之间的强制转换:

baddart
void f(JSString s) {
  s as String;
}

要检查 JS 值的类型,请使用像typeofEqualsinstanceOfString这样的互操作成员来检查 JS 值本身:

gooddart
void f(JSAny a) {
  // 在这里, `a` 被验证为 JS 函数,因此强制转换是可以的。
  if (a.typeofEquals('function')) {
    a as JSFunction;
  }
}

从 Dart 3.4 开始,您可以使用isA辅助函数来检查值是否为任何互操作类型:

gooddart
void f(JSAny a) {
  if (a.isA<JSString>()) {} // `typeofEquals('string')`
  if (a.isA<JSArray>()) {} // `instanceOfString('Array')`
  if (a.isA<CustomInteropType>()) {} // `instanceOfString('CustomInteropType')`
}

根据类型参数的不同,它会将调用转换为该类型的适当类型检查。

Dart 可能会添加 lint 来使 JS 互操作类型的运行时检查更容易避免。有关更多详细信息,请参阅问题#4841

nullundefined

#

JS 同时具有 nullundefined 值。这与 Dart 相反,Dart 只有 null 。为了使 JS 值更易于使用,如果互操作成员返回 JS nullundefined ,编译器会将这些值映射到 Dart null 。因此,以下示例中的 value 之类的成员可以解释为返回 JS 对象、JS nullundefined

dart
@JS()
external JSObject? get value;

如果返回类型未声明为可空类型,则如果返回的值是 JS nullundefined ,则程序将抛出错误以确保健全性。

JSBoxedDartObjectExternalDartReference

#

从 Dart 3.4 开始,JSBoxedDartObjectExternalDartReference 都可用于通过 JavaScript 传递对 Dart Object 的不透明引用。但是, JSBoxedDartObject 将不透明引用包装在 JavaScript 对象中,而 ExternalDartReference 本身就是引用,因此它不是 JS 类型。

如果您需要 JS 类型,或者您需要额外的检查以确保 Dart 值不会传递到另一个 Dart 运行时,请使用 JSBoxedDartObject 。例如,如果需要将 Dart 对象放入 JSArray 或传递给接受 JSAny 的 API,请使用 JSBoxedDartObject 。否则,请使用 ExternalDartReference ,因为它速度更快。

请参阅toExternalReferencetoDartObject以转换到和来自 ExternalDartReference