メインコンテンツにスキップ

JS 型

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

重要なのは、これらの型はWasmにコンパイルするかJSにコンパイルするかによって再化される方法が異なることです。これは、それらのランタイム型が異なることを意味し、したがって、isチェックとasキャストを使用できないということです。これらのJS値と対話し、調査するためには、externalインターオペレーションメンバーまたは変換を使用する必要があります。

型の階層

#

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

  • トップ型: JSAny、これはnullishでないJS値すべてです
    • プリミティブ: JSNumberJSBooleanJSString
    • JSSymbol
    • JSBigInt
    • JSObject、これはJSオブジェクトすべてです
      • JSFunction
        • JSExportedDartFunction、これはJS関数に変換されたDartコールバックを表します
      • JSArray
      • JSPromise
      • JSDataView
      • JSTypedArray
        • JSUint8ArrayのようなJS型配列
      • JSBoxedDartObject、これはユーザーが同じDartランタイム内でDart値をボクシングして不透明に渡すことを可能にします
        • Dart 3.4以降、dart:js_interopの型ExternalDartReferenceもユーザーがDart値を不透明に渡すことを可能にしますが、JS型ではありません。各オプション間のトレードオフについては、こちらで詳しく学んでください。

各型の定義は、dart:js_interop APIドキュメントで見つけることができます。

変換

#

あるドメインの値を使用するために別のドメインに渡す場合、おそらくその値を別のドメインの対応する型に変換したいでしょう。たとえば、DartのList<JSString>を、JS型JSArray<JSString>で表される文字列のJS配列に変換して、JSインターオペレーションAPIに配列を渡せるようにしたい場合があります。

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
JSExportedDartFunction関数
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、またはプリミティブを使用することを要求します。これらはコンパイラによって暗黙的に変換されます。たとえば、これらは許可されます

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

@JS()
external InteropType get interopType;
gooddart
@JS()
external void externalDartReference(ExternalDartReference _);

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

baddart
@JS()
external Function get function;
baddart
@JS()
external set list(List _);

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

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

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

#

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

baddart
void f(JSAny a) {
  if (a is String) { … }
}
baddart
void f(JSAny a) {
  if (a is JSObject) { … }
}

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

baddart
void f(JSString s) {
  s as String;
}

JS値を型チェックするには、typeofEqualsまたはinstanceOfStringのようなインターオペレーションメンバーを使用して、JS値自体を検査してください

gooddart
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ヘルパー関数を使用して、値がインターオペレーション型であるかどうかを確認できます

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

型パラメータに応じて、適切な型チェックに呼び出しを変換します。

Dartは、JSインターオペレーション型とのランタイムチェックを回避しやすくするために、linterを追加する場合があります。詳細については、issue #4841を参照してください。

null vs undefined

#

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

dart
@JS()
external JSObject? get value;

戻り型がnullableとして宣言されていない場合、値がJS nullまたはundefinedを返すと、健全性を確保するためにプログラムはエラーをスローします。

JSBoxedDartObject vs ExternalDartReference

#

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

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

ExternalDartReferenceとの変換については、toExternalReferencetoDartObjectを参照してください。