目次

JS型

Dart値とJS値は、別々の言語ドメインに属します。Wasmにコンパイルする場合、別々のランタイムでも実行されます。そのため、JS値は外部型として扱う必要があります。JS値にDart型を提供するために、dart:js_interopは、JSで始まる「JS型」と呼ばれる一連の型を公開しています。これらの型は、コンパイル時にDart値とJS値を区別するために使用されます。

重要な点として、これらの型はWasmまたはJSにコンパイルするかどうかによって、異なる方法で具象化されます。つまり、ランタイム型が異なり、そのためisチェックとasキャストを使用することはできません。これらのJS値を操作して調べるには、externalインターオプメンバーまたは変換を使用する必要があります。

型階層

#

JS型は自然な型階層を形成します。

  • 最上位型: JSAny(null以外のJS値)
    • プリミティブ: JSNumberJSBooleanJSString
    • JSSymbol
    • JSBigInt
    • JSObject(任意のJSオブジェクト)
      • JSFunction
        • JSExportedDartFunction(JS関数に変換されたDartコールバックを表す)
      • JSArray
      • JSPromise
      • JSDataView
      • JSTypedArray
        • JSUint8ArrayなどのJS型付き配列
      • JSBoxedDartObject(ユーザーがDart値を同じDartランタイム内で不透明にボックス化して渡すことを許可する)
        • Dart 3.4以降、dart:js_interopExternalDartReference型も、ユーザーがDart値を不透明に渡すことを許可しますが、JS型ではありません。各オプションのトレードオフの詳細についてはこちらをご覧ください。

dart:js_interop APIドキュメントで、各型の定義を確認できます。

変換

#

一方のドメインからもう一方のドメインの値を使用するには、値をもう一方のドメインの対応する型に変換する必要があるでしょう。たとえば、Dart List<JSString>をJSインターオプAPIに渡せるように、JS文字列のJS配列(JS型JSArray<JSString>で表される)に変換したい場合があります。

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_interopDart型
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またはプリミティブを使用する必要があります。これらは許可されます。

良好dart
@JS()
external void primitives(String a, int b, double c, num d, bool e);
良好dart
@JS()
external JSArray jsTypes(JSObject _, JSString __);
良好dart
extension type InteropType(JSObject _) implements JSObject {}

@JS()
external InteropType get interopType;
良好dart
@JS()
external void externalDartReference(ExternalDartReference _);

一方、これらはエラーを返します。

不良dart
@JS()
external Function get function;
不良dart
@JS()
external set list(List _);

これらの要件は、Function.toJSを使用してDart関数をJSで呼び出し可能にする場合にも存在します。このコールバックに出入りする値は、互換性のあるインターオプ型またはプリミティブである必要があります。

StringなどのDartプリミティブを使用する場合、コンパイラで暗黙の変換が行われ、その値をJS値からDart値に変換します。パフォーマンスが重要で、文字列の内容を調べる必要がない場合は、2番目の例のように、変換コストを回避するために代わりにJSStringを使用する方が理にかなっている場合があります。

互換性、型チェック、キャスト

#

JS型のランタイム型は、コンパイラによって異なる場合があります。これは、ランタイム型チェックとキャストに影響します。したがって、値がインターオプ型である場合、またはターゲット型がインターオプ型である場合は、ほとんどの場合、isチェックを避けてください。

不良dart
void f(JSAny a) {
  if (a is String) { … }
}
不良dart
void f(JSAny a) {
  if (a is JSObject) { … }
}

また、Dart型とインターオプ型間のキャストも避けてください。

不良dart
void f(JSString s) {
  s as String;
}

JS値の型チェックを行うには、JS値自体を調べるtypeofEqualsまたはinstanceOfStringなどのインターオプメンバーを使用します。

良好dart
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ヘルパー関数を使用して、値がインターオプ型かどうかを確認できます。

良好dart
void f(JSAny a) {
  if (a.isA<JSString>()) {} // `typeofEquals('string')`
  if (a.isA<JSArray>()) {} // `instanceOfString('Array')`
  if (a.isA<CustomInteropType>()) {} // `instanceOfString('CustomInteropType')`
}

型パラメータに応じて、その型に適切な型チェックに変換されます。

Dartは、JSインターオプ型を使用したランタイムチェックをより簡単に回避できるように、lintを追加する可能性があります。詳細については、issue #4841を参照してください。

nullundefined

#

JSには、nullundefinedの両方の値があります。これは、nullしかないDartとは対照的です。JS値の使いやすさを向上させるために、インターオプメンバーがJSのnullまたはundefinedのいずれかを返す場合、コンパイラはこれらの値をDartのnullにマッピングします。したがって、次の例のようなvalueメンバーは、JSオブジェクト、JS null、またはundefinedを返すものと解釈できます。

dart
@JS()
external JSObject? get value;

返り値の型がnull許容型として宣言されていなかった場合、返された値がJSのnullまたはundefinedであった場合、プログラムはエラーをスローします。これは健全性を確保するためです。

JSBoxedDartObjectExternalDartReference

#

Dart 3.4以降、JSBoxedDartObjectExternalDartReferenceの両方を使用して、DartのObjectへの不透明な参照をJavaScript経由で渡すことができます。ただし、JSBoxedDartObjectは不透明な参照をJavaScriptオブジェクトでラップしますが、ExternalDartReferenceは参照そのものであり、JS型ではありません。

JS型が必要な場合、またはDart値が別のDartランタイムに渡されないことを確認するための追加のチェックが必要な場合は、JSBoxedDartObjectを使用してください。たとえば、DartオブジェクトをJSArrayに配置する必要がある場合、またはJSAnyを受け入れるAPIに渡す必要がある場合は、JSBoxedDartObjectを使用します。それ以外の場合は、ExternalDartReferenceを使用してください。これは高速です。

ExternalDartReferenceとの間で変換するには、toExternalReferencetoDartObjectを参照してください。