目錄

JS 類型

Dart 值和 JS 值屬於不同的語言領域。當編譯成 Wasm 時,它們也會在不同的執行階段執行。因此,您應該將 JS 值視為外部類型。為了為 JS 值提供 Dart 類型,dart:js_interop 公開了一組以 JS 為字首的類型,稱為「JS 類型」。這些類型用於在編譯時區分 Dart 值和 JS 值。

重要的是,這些類型的具體化方式會根據您編譯成 Wasm 或 JS 而有所不同。這表示它們的執行階段類型會不同,因此您無法使用 is 檢查和 as 轉換。為了與這些 JS 值互動並檢查它們,您應該使用 external 互通成員或轉換

類型階層

#

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 或基本類型,然後編譯器會隱式轉換。例如,這些都是允許的

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

@JS()
external InteropType get interopType;
良好dart
@JS()
external void externalDartReference(ExternalDartReference _);

而這些會傳回錯誤

不良dart
@JS()
external Function get function;
不良dart
@JS()
external set list(List _);

當您使用 Function.toJS 使 Dart 函式在 JS 中可呼叫時,也會有相同的要求。流入和流出此回呼的值必須是相容的互通類型或基本類型。

如果您使用 String 等 Dart 基本類型,編譯器會進行隱式轉換,將該值從 JS 值轉換為 Dart 值。如果效能至關重要,而且您不需要檢查字串的內容,那麼使用 JSString 來避免轉換成本可能會有意義,例如第二個範例所示。

相容性、類型檢查和轉換

#

JS 類型的執行階段類型可能會根據編譯器而有所不同。這會影響執行階段類型檢查和轉換。因此,幾乎始終避免 is 檢查,其中值是互通類型或目標類型是互通類型

不良dart
void f(JSAny a) {
  if (a is String) { … }
}
不良dart
void f(JSAny a) {
  if (a is JSObject) { … }
}

此外,請避免在 Dart 類型和互通類型之間進行轉換

不良dart
void f(JSString s) {
  s as String;
}

若要對 JS 值進行型別檢查,請使用像是 typeofEqualsinstanceOfString 這類的互通成員,它們會檢查 JS 值本身。

良好dart
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 輔助函式來檢查值是否為任何互通型別。

良好dart
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 互通型別的執行時期檢查更容易避免。有關更多詳細資訊,請參閱 issue #4841

nullundefined

#

JS 具有 nullundefined 值。這與僅具有 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