用法
JS interop 提供了從 Dart 與 JavaScript API 互動的機制。它讓您可以使用明確且符合語言習慣的語法來調用這些 API,並與從它們取得的值互動。
通常,您可以透過將 JavaScript API 放在全域 JS 範圍中的某處來存取它。若要從此 API 呼叫和接收 JS 值,您可以使用external
interop 成員。若要建構 JS 值的類型並提供類型,您可以使用和宣告 interop 類型,其中也包含 interop 成員。若要將 Dart 值(例如 List
或 Function
)傳遞至 interop 成員,或將 JS 值轉換為 Dart 值,您可以使用轉換函式,除非 interop 成員包含原始類型。
Interop 類型
#當與 JS 值互動時,您需要為其提供 Dart 類型。您可以透過使用或宣告 interop 類型來執行此操作。Interop 類型可以是 Dart 提供的「JS 類型」,也可以是包裝 interop 類型的擴充類型。
Interop 類型可讓您為 JS 值提供介面,並讓您為其成員宣告 interop API。它們也用於其他 interop API 的簽章中。
extension type Window(JSObject _) implements JSObject {}
Window
是任意 JSObject
的 interop 類型。無法在執行階段保證 Window
實際上是 JS Window
。對於為相同值定義的任何其他 interop 介面,也不會發生衝突。如果您想要檢查 Window
實際上是否為 JS Window
,您可以透過 interop 檢查 JS 值的類型。
您也可以透過包裝 JS 類型來宣告自己的 JS 類型 interop 類型
extension type Array._(JSArray<JSAny?> _) implements JSArray<JSAny?> {
external Array();
}
在大多數情況下,您可能會宣告使用 JSObject
作為表示類型的 interop 類型,因為您可能與沒有 Dart 提供的 interop 類型的 JS 物件互動。
Interop 類型通常也應該實作其表示類型,以便它們可以在預期表示類型的地方使用,例如在 package:web
提供的許多 API 中。
Interop 成員
#external
interop 成員為 JS 成員提供慣用的語法。它們可讓您為其引數和傳回值撰寫 Dart 類型簽章。可以在這些成員的簽章中撰寫的類型具有限制。interop 成員對應的 JS API 由其宣告位置、名稱、Dart 成員的類型以及任何重新命名的組合來決定。
頂層 Interop 成員
#假設有下列 JS 成員
globalThis.name = 'global';
globalThis.isNameEmpty = function() {
return globalThis.name.length == 0;
}
您可以為它們撰寫 interop 成員,如下所示
@JS()
external String get name;
@JS()
external set name(String value);
@JS()
external bool isNameEmpty();
在這裡,全域範圍中存在屬性 name
和函式 isNameEmpty
。若要存取它們,您可以使用頂層 interop 成員。若要取得和設定 name
,請宣告並使用具有相同名稱的 interop getter 和 setter。若要使用 isNameEmpty
,請宣告並呼叫具有相同名稱的 interop 函式。您可以宣告頂層 interop getter、setter、方法和欄位。Interop 欄位相當於 getter 和 setter 配對。
頂層 interop 成員必須使用 @JS()
註解宣告,以便將它們與其他 external
頂層成員區分開來,例如可以使用 dart:ffi
撰寫的成員。
Interop 類型成員
#假設有如下所示的 JS 介面
class Time {
constructor(hours, minutes) {
this._hours = Math.abs(hours) % 24;
this._minutes = arguments.length == 1 ? 0 : Math.abs(minutes) % 60;
}
static dinnerTime = new Time(18, 0);
static getTimeDifference(t1, t2) {
return new Time(t1.hours - t2.hours, t1.minutes - t2.minutes);
}
get hours() {
return this._hours;
}
set hours(value) {
this._hours = Math.abs(value) % 24;
}
get minutes() {
return this._minutes;
}
set minutes(value) {
this._minutes = Math.abs(value) % 60;
}
isDinnerTime() {
return this.hours == Time.dinnerTime.hours && this.minutes == Time.dinnerTime.minutes;
}
}
// Need to expose the type to the global scope.
globalThis.Time = Time;
您可以為其撰寫 interop 介面,如下所示
extension type Time._(JSObject _) implements JSObject {
external Time(int hours, int minutes);
external factory Time.onlyHours(int hours);
external static Time dinnerTime;
external static Time getTimeDifference(Time t1, Time t2);
external int hours;
external int minutes;
external bool isDinnerTime();
bool isMidnight() => hours == 0 && minutes == 0;
}
在 interop 類型中,您可以宣告幾種不同類型的 external
interop 成員
建構子。呼叫時,僅具有位置參數的建構子會建立新的 JS 物件,其建構子由使用
new
的擴充類型名稱定義。例如,在 Dart 中呼叫Time(0, 0)
會產生看起來像new Time(0, 0)
的 JS 調用。同樣地,呼叫Time.onlyHours(0)
會產生看起來像new Time(0)
的 JS 調用。請注意,兩個建構子的 JS 調用遵循相同的語意,無論它們是否被賦予 Dart 名稱,或者它們是否為 factory。物件實值建構子。有時建立 JS 物件實值會很有用,它只包含一些屬性及其值。為了做到這一點,請宣告一個僅具有具名參數的建構子,其中參數的名稱與屬性名稱相符
dartextension type Options._(JSObject o) implements JSObject { external Options({int a, int b}); external int get a; external int get b; }
呼叫
Options(a: 0, b: 1)
會產生建立 JS 物件{a: 0, b: 1}
的結果。物件由調用引數定義,因此呼叫Options(a: 0)
會產生{a: 0}
的結果。您可以透過external
實例成員取得或設定物件的屬性。
static
成員。與建構子類似,靜態成員使用擴充類型的名稱來產生 JS 程式碼。例如,呼叫Time.getTimeDifference(t1, t2)
會產生看起來像Time.getTimeDifference(t1, t2)
的 JS 調用。同樣地,呼叫Time.dinnerTime
會產生看起來像Time.dinnerTime
的 JS 調用。與頂層成員類似,您可以宣告static
方法、getter、setter 和欄位。實例成員。與其他 Dart 類型一樣,實例成員需要使用實例。這些成員會取得、設定或調用實例上的屬性。例如
dartfinal time = Time(0, 0); print(time.isDinnerTime()); // false final dinnerTime = Time.dinnerTime; time.hours = dinnerTime.hours; time.minutes = dinnerTime.minutes; print(time.isDinnerTime()); // true
呼叫
dinnerTime.hours
會取得dinnerTime
的hours
屬性的值。同樣地,呼叫time.minutes=
會設定 time 的minutes
屬性的值。呼叫time.isDinnerTime()
會呼叫time
的isDinnerTime
屬性中的函式,並傳回值。與頂層和static
成員類似,您可以宣告實例方法、getter、setter 和欄位。運算子。在 interop 類型中,只允許兩個
external
interop 運算子:[]
和[]=
。這些是與 JS 屬性存取器語意相符的實例成員。例如,您可以像這樣宣告它們dartextension type Array(JSArray<JSNumber> _) implements JSArray<JSNumber> { external JSNumber operator [](int index); external void operator []=(int index, JSNumber value); }
呼叫
array[i]
會取得array
的第i
個槽中的值,而array[i] = i.toJS
會將該槽中的值設定為i.toJS
。其他 JS 運算子由dart:js_interop
中的公用程式函式公開。
最後,與任何其他擴充類型一樣,您可以在 interop 類型中宣告任何非 external
成員。使用 interop 值的布林 getter isMidnight
就是這樣一個範例。
Interop 類型上的擴充成員
#您也可以在 interop 類型的擴充中撰寫 external
成員。例如
extension on Array {
external int push(JSAny? any);
}
呼叫 push
的語意與它在 Array
的定義中時相同。擴充可以具有 external
實例成員和運算子,但不能具有 external
static
成員或建構子。與 interop 類型一樣,您可以在擴充中撰寫任何非 external
成員。當 interop 類型未公開您需要的 external
成員,並且您不想建立新的 interop 類型時,這些擴充很有用。
參數
#external
interop 方法只能包含位置和選用引數。這是因為 JS 成員僅採用位置引數。唯一的例外是物件實值建構子,它們只能包含具名引數。
與非 external
方法不同,選用引數不會被其預設值取代,而是會被省略。例如
external int push(JSAny? any, [JSAny? any2]);
在 Dart 中呼叫 array.push(0.toJS)
會產生 array.push(0.toJS)
而非 array.push(0.toJS, null)
的 JS 調用。這讓使用者不必為相同的 JS API 撰寫多個 interop 成員,以避免傳遞 null
。如果您宣告具有明確預設值的參數,您會收到警告,指出該值將被忽略。
@JS()
#有時以與撰寫名稱不同的名稱來參照 JS 屬性會很有用。例如,如果您想要撰寫指向相同 JS 屬性的兩個 external
API,您需要為至少其中一個撰寫不同的名稱。同樣地,如果您想要定義參照相同 JS 介面的多個 interop 類型,您需要重新命名至少其中一個。另一個範例是 JS 名稱無法在 Dart 中撰寫,例如 $a
。
若要執行此操作,您可以使用具有常數字串值的 @JS()
註解。例如
extension type Array._(JSArray<JSAny?> _) implements JSArray<JSAny?> {
external int push(JSNumber number);
@JS('push')
external int pushString(JSString string);
}
呼叫 push
或 pushString
都會產生使用 push
的 JS 程式碼。
您也可以重新命名 interop 類型
@JS('Date')
extension type JSDate._(JSObject _) implements JSObject {
external JSDate();
external static int now();
}
呼叫 JSDate()
會產生 new Date()
的 JS 調用。同樣地,呼叫 JSDate.now()
會產生 Date.now()
的 JS 調用。
此外,您可以為整個程式庫命名空間,為這些類型中的所有 interop 頂層成員、interop 類型和 static
interop 成員新增前置詞。如果您想要避免將太多成員新增至全域 JS 範圍,這會很有用。
@JS('library1')
library;
import 'dart:js_interop';
@JS()
external void method();
extension type JSType._(JSObject _) implements JSObject {
external JSType();
external static int get staticMember;
}
呼叫 method()
會產生 library1.method()
的 JS 調用,呼叫 JSType()
會產生 new library1.JSType()
的 JS 調用,而呼叫 JSType.staticMember
會產生 library1.JSType.staticMember
的 JS 調用。
與 interop 成員和 interop 類型不同,Dart 僅在您在程式庫上的 @JS()
註解中提供非空值時,才會在 JS 調用中新增程式庫名稱。它不會使用程式庫的 Dart 名稱作為預設值。
library interop_library;
import 'dart:js_interop';
@JS()
external void method();
呼叫 method()
會產生 method()
而非 interop_library.method()
的 JS 調用。
您也可以為程式庫、頂層成員和 interop 類型撰寫以 .
分隔的多個命名空間
@JS('library1.library2')
library;
import 'dart:js_interop';
@JS('library3.method')
external void method();
@JS('library3.JSType')
extension type JSType._(JSObject _) implements JSObject {
external JSType();
}
呼叫 method()
會產生 library1.library2.library3.method()
的 JS 調用,呼叫 JSType()
會產生 new library1.library2.library3.JSType()
的 JS 調用,依此類推。
但是,您無法在 interop 類型成員或 interop 類型的擴充成員上,將 @JS()
註解與值中的 .
搭配使用。
如果未提供值給 @JS()
,或值為空,則不會發生重新命名。
@JS()
也會告知編譯器,成員或類型預期會被視為 JS interop 成員或類型。所有頂層成員都需要它(無論是否具有值),以便將它們與其他 external
頂層成員區分開來,但在 interop 類型內部以及擴充成員上通常可以省略它,因為編譯器可以從表示類型和 on-type 判斷它是 JS interop 類型。
將 Dart 函式和物件匯出至 JS
#前面的章節說明如何從 Dart 呼叫 JS 成員。匯出 Dart 程式碼,使其可以在 JS 中使用也很有用。若要將 Dart 函式匯出至 JS,請先使用 Function.toJS
轉換它,這會使用 JS 函式包裝 Dart 函式。然後,透過 interop 成員將包裝的函式傳遞至 JS。此時,它就可以被其他 JS 程式碼呼叫。
例如,此程式碼會轉換 Dart 函式,並使用 interop 將其設定在全域屬性中,然後在 JS 中呼叫它
import 'dart:js_interop';
@JS()
external set exportedFunction(JSFunction value);
void printString(JSString string) {
print(string.toDart);
}
void main() {
exportedFunction = printString.toJS;
}
globalThis.exportedFunction('hello world');
以此方式匯出的函式具有與 interop 成員類型限制類似的類型。
有時匯出整個 Dart 介面會很有用,以便 JS 可以與 Dart 物件互動。若要執行此操作,請使用 @JSExport
將 Dart 類別標記為可匯出,並使用 createJSInteropWrapper
包裝該類別的實例。如需更詳細的技術說明,包括如何模擬 JS 值,請查看如何模擬 JavaScript interop 物件。
dart:js_interop
和 dart:js_interop_unsafe
#dart:js_interop
包含您應該需要的所有必要成員,包括 @JS
、JS 類型、轉換函式和各種公用程式函式。公用程式函式包括
globalContext
,它代表編譯器用來尋找 interop 成員和類型的全域範圍。- 協助程式以檢查 JS 值的類型
- JS 運算子
dartify
和jsify
,它們會檢查特定 JS 值的類型,並將它們轉換為 Dart 值,反之亦然。當您知道 JS 值的類型時,最好使用特定的轉換,因為額外的類型檢查可能會很耗費效能。importModule
,可讓您將模組動態匯入為JSObject
。isA
,可讓您檢查 JS interop 值是否為類型引數指定的 JS 類型的實例。
未來可能會將更多公用程式新增至此程式庫。
dart:js_interop_unsafe
包含可讓您動態查閱屬性的成員。例如
JSFunction f = console['log'];
可以使用字串來存取屬性,而不是宣告名為 log
的 interop 成員。dart:js_interop_unsafe
也提供動態檢查、取得、設定和呼叫屬性的函式。
除非另有說明,否則本網站上的文件反映的是 Dart 3.7.1 版本。頁面上次更新於 2025-01-22。 檢視原始碼 或 回報問題。