跳到主要內容

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 類型。請這裡瞭解更多關於每個選項的權衡。

您可以在 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) {
  // 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 可能會新增 lints,以使執行階段檢查 JS 互通類型更容易避免。請參閱 issue #4841 以瞭解更多詳細資訊。

nullundefined

#

JS 同時具有 nullundefined 值。這與僅具有 null 的 Dart 形成對比。為了使 JS 值更符合人體工學地使用,如果互通成員要傳回 JS nullundefined,則編譯器會將這些值對應到 Dart null。因此,以下範例中的 value 成員可以解釋為傳回 JS 物件、JS nullundefined

dart
@JS()
external JSObject? get value;

如果傳回類型未宣告為可為 null,則如果傳回的值是 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 轉換。