JS 型別
Dart 值和 JS 值屬於不同的語言域。當編譯到 Wasm 時,它們也在單獨的執行時中執行。因此,您應該將 JS 值視為外部型別。為了給 JS 值提供 Dart 型別,dart:js_interop 暴露了一組以 JS 為字首的型別,稱為“JS 型別”。這些型別用於在編譯時區分 Dart 值和 JS 值。
重要的是,這些型別的具體化方式取決於您是編譯到 Wasm 還是 JS。這意味著它們的執行時型別會不同,因此您不能使用 is 檢查和 as 型別轉換。為了與這些 JS 值互動和檢查它們,您應該使用外部互操作成員或轉換。
型別層級結構
#JS 型別構成了一個自然的型別層級結構
- 頂層型別:
JSAny,表示任何非空 JS 值- 基本型別:
JSNumber、JSBoolean、JSString JSSymbolJSBigIntJSObject,表示任何 JS 物件JSFunctionJSExportedDartFunction,表示轉換為 JS 函式的 Dart 回撥
JSArrayJSPromiseJSDataViewJSTypedArray- JS 型別化陣列,例如
JSUint8Array
- JS 型別化陣列,例如
JSBoxedDartObject,允許使用者在同一 Dart 執行時內封裝並透明地傳遞 Dart 值- 從 Dart 3.4 開始,
dart:js_interop中的型別ExternalDartReference也允許使用者透明地傳遞 Dart 值,但它不是 JS 型別。請此處瞭解更多關於每種選項的權衡。
- 從 Dart 3.4 開始,
- 基本型別:
您可以在 dart:js_interop API 文件中找到每種型別的定義。
轉換
#要在一個域中使用另一個域的值,您可能希望將該值轉換為另一個域的相應型別。例如,您可能希望將 Dart 的 List<JSString> 轉換為 JS 字串陣列(由 JS 型別 JSArray<JSString> 表示),以便可以將該陣列傳遞給 JS 互操作 API。
Dart 在各種 Dart 型別和 JS 型別上提供了一些轉換成員,用於在域之間轉換值。
將值從 Dart 轉換為 JS 的成員通常以 toJS 開頭
String str = 'hello world';
JSString jsStr = str.toJS;將值從 JS 轉換為 Dart 的成員通常以 toDart 開頭
JSNumber jsNum = ...;
int integer = jsNum.toDartInt;並非所有 JS 型別都支援轉換,也並非所有 Dart 型別都支援轉換。通常,轉換表如下所示
dart:js_interop 型別 | Dart 型別 |
|---|---|
JSNumber、JSBoolean、JSString | num、int、double、bool、String |
JSExportedDartFunction | Function |
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 或原始型別,這些型別會由編譯器隱式轉換。例如,以下情況是被允許的
@JS()
external void primitives(String a, int b, double c, num d, bool e);@JS()
external JSArray jsTypes(JSObject _, JSString __);extension type InteropType(JSObject _) implements JSObject {}
@JS()
external InteropType get interopType;@JS()
external void externalDartReference(ExternalDartReference _);而以下情況會返回錯誤
@JS()
external Function get function;@JS()
external set list(List _);當您使用 Function.toJS 使 Dart 函式可在 JS 中呼叫時,也存在同樣的要求。進出此回撥的值必須是相容的互操作型別或原始型別。
如果您使用像 String 這樣的 Dart 原始型別,編譯器會進行隱式轉換,將該值從 JS 值轉換為 Dart 值。如果效能至關重要,並且您不需要檢查字串的內容,那麼使用 JSString 代替以避免轉換成本可能是有意義的,就像第二個例子中那樣。
相容性、型別檢查和型別轉換
#JS 型別的執行時型別可能因編譯器而異。這會影響執行時型別檢查和型別轉換。因此,幾乎總是避免對互操作型別的值或目標型別為互操作型別的進行 is 檢查
void f(JSAny a) {
if (a is String) { … }
}void f(JSAny a) {
if (a is JSObject) { … }
}此外,避免在 Dart 型別和互操作型別之間進行型別轉換
void f(JSString s) {
s as String;
}要對 JS 值進行型別檢查,請使用檢查 JS 值本身的互操作成員,例如 typeofEquals 或 instanceOfString
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 輔助函式來檢查值是否為任何互操作型別
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 以獲取更多詳細資訊。
null 與 undefined
#JS 既有 null 值,也有 undefined 值。這與只有 null 的 Dart 不同。為了使 JS 值更易於使用,如果互操作成員返回 JS 的 null 或 undefined,編譯器會將這些值對映到 Dart 的 null。因此,以下示例中的 value 這樣的成員可以解釋為返回 JS 物件、JS 的 null 或 undefined
@JS()
external JSObject? get value;如果返回型別未宣告為可空,則如果返回的值是 JS 的 null 或 undefined,程式將丟擲錯誤以確保健全性。
JSBoxedDartObject 與 ExternalDartReference
#從 Dart 3.4 開始,JSBoxedDartObject 和 ExternalDartReference 都可以用來透過 JavaScript 傳遞對 Dart Object 的不透明引用。然而,JSBoxedDartObject 將不透明引用封裝在一個 JavaScript 物件中,而 ExternalDartReference 本身就是引用,因此不是 JS 型別。
如果您需要 JS 型別,或者需要額外檢查以確保 Dart 值不會被傳遞到另一個 Dart 執行時,請使用 JSBoxedDartObject。例如,如果 Dart 物件需要放在 JSArray 中或傳遞給接受 JSAny 的 API,請使用 JSBoxedDartObject。否則請使用 ExternalDartReference,因為它會更快。
請參閱 toExternalReference 和 toDartObject 來進行與 ExternalDartReference 之間的轉換。