Flutterの新しいjs_interopまとめ
ついに、package:jsがdiscontinuedとなり、dart:jsもあまり推奨されずjs_interop
にそろそろ移行した方が良さそうな感じになりました。
しかし、新しいことで、情報が少ない上に、Flutter webを使っているという少ない人口の上に、ネイティブのコードを扱うという非常に情報が見つかりにくいので、わかったことを、まとめておきます。
js_interop
って
そもそも、dart:js
とpackage:js
は、どちらもflutter
のwasm
ビルドに対応していません。
まず、js_interop
という名前は、JS
とinteroperability
という言葉からできていますが、interoperability
という言葉を初めて聴いた人も多いのではないでしょうか。interoperability
は「相互互換性」という意味で、dart
とjavascript
とwasm
の両方を同じページで共存させ、相互の橋渡しをするという意味だと思われます。
難しいことは考えず、新しい「DartとJSをつなぐ道具」と考えれば大丈夫です。
使い方
Javascriptという非厳密な言語との橋渡しで、型的に安全ではないコードになりがちなので、少しずつ試しながら、再確認しながら、定義しないと、後でやってみてうまくいかなくて、エラーの内容が空っぽだったり意味不明だったり、スタックトレースが難読化されていたりと大変なことになるかもしれません。
インポート
import 'dart:js_interop';
最近、以下のように書いてエラーになって数時間悩んだので、注意
import 'dart:js_interop/js_interop.dart';
Webなのに、こんなことを言われてしまいます。
Dart library 'dart:js_interop/js_interop.dart' is not available on this platform.
index.html
など)の関数を、Dartから呼び出す)
Dart => JS (JS(JSの関数をDartから呼び出すときは、まずDartでJSの関数やクラスの設計図を定義します。このことで、Dartの中では型安全に使うことができます。ただ、もちろん、その設計図が間違っていたら、型安全でなくなってしまうので、JS側のAPIの仕様を確認してから書いた方が良さそうですね。
関数
というわけで、まず、Window.alert
の橋渡しを定義してみましょう。
import 'dart:js_interop';
("alert")
external void dartAlert(String alert); // 厳密には文字列に変換できるオブジェクトを受け入れるが、APIがString「も」許容すれば大丈夫
void main() {
dartAlert("Hello World!");
}
external
というのは、名前の通り外部のどこかで定義するからここでは書かない、という意味です。まあ、js_interop
ぐらいでしかあまり使われない気もします。
@JS
、のようなものは、メタデータ/アノテーションと呼ばれるものです。js_interop
に限らず、例えば@override
や、@deprecated
などのアノテーションがあります。Python
のデコレーターと違い、直接関数の動きを変えたりせず、ある意味ただのマークとしての意味を果たします。
@JS
はexternal
で定義された関数が実行されたときにJSの関数を呼び出すのが使命で、引数を省略すると、dartAlert
という関数をJSで実行します。命名規則がJSと違うときなどに引数を使うと便利です。
クラス
私が調べた限りでは、2通りの定義のやり方があります。
なんか、私にはエラー文の方が分かりやすかったので、一応載せておきます。
エラー文
compileNewDDC
main.dart:4:15: Error: External JS interop member contains invalid types in its function signature: 'void Function(*dynamic*)'.
Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.
external void jsLog(dynamic message);
^
extension
公式の解説がわかりやすいので(英語ですが)、それもぜひ読んでみて下さい。
extension type Person._(JSObject _) implements JSObject {
external Person(String firstName, String lastName, int age);
external factory Person.onlyName(String firstName, String lastName);
external int name;
external int age;
external bool getFullName();
bool getIsAdult() => age >= adultThreshold;
external static Person adultThreshold; // 18
external static Person getAgeDifference(Person person1, Person person2);
}
@JS
()
class Person {
external factory Person(String firstName, String lastName, int age);
external factory Person.onlyName(String firstName, String lastName);
}
extension PersonExtension on Person {
external String get firstName;
external String get lastName;
external num get age;
external bool getFullName();
}
("Person.adultThreshold")
external int personAdultThreshold;
("Person.getAgeDifference")
external int personGetAgeDifference(Person person1, Person person2);
@staticInterop
は必須で、静的なときに、@JS()
と並べて@staticInterop
を書くことができ、プロパティなどはextension
として定義できます。
ちなみに、@staticInterop
がないと、こうなります。
The '@JS' annotation from 'dart:js_interop' can only be used for static interop, either through extension types or '@staticInterop' classes.
また、interface
とかで、Dartから「クラス」にはアクセスできず、Object
になっていた場合、@JS``@staticInterop
と並べて@anonymous
が使えます。
使い分けは... 私には分かりませんでした...
Dart内の変換
toJS
おそらくextension
で定義されているので、import
を忘れずに(忘れると存在しないというようなエラーになりドキュメントを読み直すことになるかもしれません)。
print(5.toJS); // JSNumber
toDart
toJS
の反対
print(5.toJS.toDart); // 5
jsfunction
dart:js
のallowInterop
の代わりに、js_interop
ではこれを使用します。
()
void callback(int param1, String param2) {
return "$param2$param1";
}
良い習慣かどうかわかりませんが... ラッパーを作っても機能します。プラグインとかを開発するときはこのような方法がないと大変ですね。
class SuccessCallbackWrapper {
const SuccessCallbackWrapper(this.callback);
final SuccessCallback callback;
()
void successCallback(JSString decodedText, Html5QrcodeResult result) =>
callback(decodedText.toDart, result);
}
まとめ
js_interop
は、Flutter WebやDartでJavaScriptと安全かつ柔軟にやりとりするための新しい標準的な方法です。従来のdart:js
やpackage:js
からの移行が推奨されており、型安全性や将来性の面でもメリットがあります。まだ情報が少ない部分もありますが、公式ドキュメントやサンプルを参考にしながら、少しずつ試していくのが良さそうです。まだまだアップデートが増えていきそうなので、要チェックですね!
Discussion