跳過主內容

JS 型別

Dart 值和 JS 值屬於不同的語言域。當編譯到 Wasm 時,它們也在單獨的執行時中執行。因此,您應該將 JS 值視為外部型別。為了給 JS 值提供 Dart 型別,dart:js_interop 暴露了一組以 JS 為字首的型別,稱為“JS 型別”。這些型別用於在編譯時區分 Dart 值和 JS 值。

重要的是,這些型別的具體化方式取決於您是編譯到 Wasm 還是 JS。這意味著它們的執行時型別會不同,因此您不能使用 is 檢查和 as 型別轉換。為了與這些 JS 值互動和檢查它們,您應該使用外部互操作成員或轉換

型別層級結構

#

JS 型別構成了一個自然的型別層級結構

  • 頂層型別:JSAny,表示任何非空 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 型別。請此處瞭解更多關於每種選項的權衡。

您可以在 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 值進行型別檢查,請使用檢查 JS 值本身的互操作成員,例如 typeofEqualsinstanceOfString

gooddart
void f(JSAny a) {
  // Here `a` is verified to be a JS function, so the cast is okay.
  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 既有 null 值,也有 undefined 值。這與只有 null 的 Dart 不同。為了使 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 之間的轉換。