使用方法
JS相互運用は、DartからJavaScript APIと対話するためのメカニズムを提供します。明示的で慣用的な構文を使用して、これらのAPIを呼び出し、そこから取得した値と対話できます。
通常、グローバルJSスコープ内のどこかでJavaScript APIを使用可能にすることでアクセスします。このAPIからJS値を呼び出して受信するには、external
相互運用メンバーを使用します。JS値の型を作成して提供するには、相互運用型を使用および宣言します。これには相互運用メンバーも含まれます。List
やFunction
などのDart値を相互運用メンバーに渡したり、JS値からDart値に変換するには、相互運用メンバーがプリミティブ型を含む場合を除き、変換関数を使用します。
相互運用型
#JS値と対話する際には、Dart型を提供する必要があります。これは、相互運用型を使用するか、宣言することで実行できます。相互運用型は、Dartによって提供される"JS型"か、相互運用型をラップする拡張型のいずれかです。
相互運用型を使用すると、JS値のインターフェースを提供し、そのメンバーの相互運用APIを宣言できます。これらは、他の相互運用APIのシグネチャにも使用されます。
extension type Window(JSObject _) implements JSObject {}
Window
は、任意のJSObject
に対する相互運用型です。Window
が実際にJSのWindow
であるというランタイム保証はありません。また、同じ値に対して定義されている他の相互運用インターフェースとの競合もありません。Window
が実際にJSのWindow
であることを確認する場合は、相互運用を通じてJS値の型を確認できます。
Dartが提供するJS型をラップすることで、独自のJS型に対する相互運用型を宣言することもできます。
extension type Array._(JSArray<JSAny?> _) implements JSArray<JSAny?> {
external Array();
}
ほとんどの場合、Dartによって提供される相互運用型を持たないJSオブジェクトと対話している可能性が高いため、表現型としてJSObject
を使用して相互運用型を宣言する可能性が高いです。
相互運用型は、一般的に実装する必要があります。表現型が期待される場所(package:web
の多くのAPIなど)で使用できるようにするためです。
相互運用メンバー
#external
相互運用メンバーは、JSメンバーの慣用的な構文を提供します。引数と戻り値のDart型シグネチャを書くことができます。これらのメンバーのシグネチャに記述できる型には制限があります。相互運用メンバーに対応するJS APIは、宣言されている場所、名前、Dartメンバーの種類、および名前変更の組み合わせによって決定されます。
トップレベルの相互運用メンバー
#以下のJSメンバーがあるとします。
globalThis.name = 'global';
globalThis.isNameEmpty = function() {
return globalThis.name.length == 0;
}
それらに対する相互運用メンバーは、次のように記述できます。
@JS()
external String get name;
@JS()
external set name(String value);
@JS()
external bool isNameEmpty();
ここでは、グローバルスコープで公開されているプロパティname
と関数isNameEmpty
が存在します。これらにアクセスするには、トップレベルの相互運用メンバーを使用します。name
を取得および設定するには、同じ名前の相互運用ゲッターとセッターを宣言して使用します。isNameEmpty
を使用するには、同じ名前の相互運用関数を宣言して呼び出します。トップレベルの相互運用ゲッター、セッター、メソッド、フィールドを宣言できます。相互運用フィールドは、ゲッターとセッターのペアに相当します。
トップレベルの相互運用メンバーは、dart:ffi
を使用して記述できるものなど、他のexternal
トップレベルメンバーと区別するために、@JS()
アノテーションで宣言する必要があります。
相互運用型メンバー
#次のような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;
それに対する相互運用インターフェースは、次のように記述できます。
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;
}
相互運用型内では、いくつかの異なるタイプのexternal
相互運用メンバーを宣言できます。
コンストラクタ。呼び出されると、位置パラメータのみを持つコンストラクタは、
new
を使用して拡張型の名前で定義されたコンストラクタを持つ新しいJSオブジェクトを作成します。たとえば、DartでTime(0, 0)
を呼び出すと、new Time(0, 0)
のようなJS呼び出しが生成されます。同様に、Time.onlyHours(0)
を呼び出すと、new Time(0)
のようなJS呼び出しが生成されます。Dart名を与えられたか、ファクトリであるかにかかわらず、2つのコンストラクタのJS呼び出しは同じセマンティクスに従います。オブジェクトリテラルコンストラクタ。いくつかのプロパティとその値を含む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
メソッド、ゲッター、セッター、フィールドを宣言できます。インスタンスメンバー。他の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
メンバーと同様に、インスタンスメソッド、ゲッター、セッター、フィールドを宣言できます。演算子。Interop型では、許可される
external
インタロップ演算子は[]
と[]=
の2つのみです。これらは、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
ではないメンバーを宣言できます。isMidnight
はその一例です。
相互運用型に対する拡張メンバー
#Interop型の拡張機能にもexternal
メンバーを記述できます。例えば
extension on Array {
external int push(JSAny? any);
}
push
の呼び出しのセマンティクスは、Array
の定義内にある場合と同じです。拡張機能はexternal
インスタンスメンバーと演算子を持つことができますが、external
static
メンバーやコンストラクタを持つことはできません。Interop型と同様に、拡張機能にはexternal
ではないメンバーを記述できます。これらの拡張機能は、必要なexternal
メンバーがInterop型で公開されておらず、新しいInterop型を作成したくない場合に役立ちます。
パラメータ
#external
インタロップメソッドには、位置引数と省略可能な引数のみを含めることができます。これは、JSメンバーが位置引数のみを受け取るためです。例外はオブジェクトリテラルコンストラクタで、名前付き引数のみを含めることができます。
external
ではないメソッドとは異なり、省略可能な引数はデフォルト値で置き換えられるのではなく、省略されます。例えば
external int push(JSAny? any, [JSAny? any2]);
Dartでarray.push(0.toJS)
を呼び出すと、array.push(0.toJS, null)
ではなく、array.push(0.toJS)
というJS呼び出しが行われます。これにより、null
を渡すのを避けるために、同じJS APIに対して複数のインタロップメンバーを記述する必要がなくなります。明示的なデフォルト値を持つパラメータを宣言すると、その値が無視されるという警告が表示されます。
@JS()
#記述されているものとは異なる名前でJSプロパティを参照することが便利な場合があります。例えば、同じJSプロパティを指す2つのexternal
APIを記述する場合、少なくとも一方に異なる名前を付ける必要があります。同様に、同じJSインターフェースを参照する複数のInterop型を定義する場合、少なくとも1つを名前変更する必要があります。別の例としては、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()
を呼び出すと、interop_library.method()
ではなく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インタロップメンバーまたは型として扱われることをコンパイラに指示します。他のexternal
トップレベルメンバーと区別するために、すべてのトップレベルメンバーには(値の有無にかかわらず)必須ですが、コンパイラは表現型と型からJSインタロップ型であることを認識できるため、Interop型内および拡張メンバー上では多くの場合省略できます。
Dartの関数とオブジェクトをJSにエクスポートする
#上記のセクションでは、DartからJSメンバーを呼び出す方法を示しています。JSで使用できるようにDartコードをエクスポートすることも役立ちます。Dart関数をJSにエクスポートするには、まずFunction.toJS
を使用して変換します。これは、Dart関数をJS関数でラップします。次に、ラップされた関数を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メンバーと同様の制限があります。
JSがDartオブジェクトと対話できるように、Dartインターフェース全体をエクスポートすることが便利な場合があります。これを行うには、@JSExport
を使用してDartクラスをエクスポート可能としてマークし、createJSInteropWrapper
を使用してそのクラスのインスタンスをラップします。JS値のモック方法を含むこのテクニックの詳細な説明については、モックに関するチュートリアルを参照してください。
dart:js_interop
とdart:js_interop_unsafe
#dart:js_interop
には、@JS
、JS型、変換関数、およびさまざまなユーティリティ関数など、必要なメンバーがすべて含まれています。ユーティリティ関数には、
- コンパイラがInteropメンバーと型を見つけるために使用するグローバルスコープを表す
globalContext
。 - JS値の型を検査するためのヘルパー。
- JS演算子。
- 特定のJS値の型をチェックし、Dart値と相互に変換する
dartify
とjsify
。JS値の型がわかっている場合は、特定の変換を使用することをお勧めします。追加の型チェックは高価になる可能性があります。 - モジュールを
JSObject
として動的にインポートできるimportModule
。
このライブラリには、将来的にさらに多くのユーティリティが追加される可能性があります。
dart:js_interop_unsafe
には、プロパティを動的に検索できるメンバーが含まれています。例えば
JSFunction f = console['log'];
log
という名前のInteropメンバーを宣言する代わりに、文字列を使用してプロパティを表しています。dart:js_interop_unsafe
は、プロパティを動的に取得、設定、および呼び出す機能を提供します。
特に明記されていない限り、このサイトのドキュメントはDart 3.5.3を反映しています。ページの最終更新日:2024年8月6日。 ソースを表示 または 問題を報告する。