跳到主內容

如何模擬 JavaScript 互操作物件

在本教程中,你將學習如何模擬 JS 物件,以便在不使用真實實現的情況下測試互操作例項成員。

背景與動機

#

在 Dart 中模擬類通常透過覆蓋例項成員來實現。然而,由於使用擴充套件型別來宣告互操作型別,所有擴充套件型別成員都是靜態分派的,因此不能使用覆蓋。此限制也適用於擴充套件成員,因此無法模擬例項擴充套件型別或擴充套件成員。

雖然這適用於任何非external擴充套件型別成員,但external互操作成員是特殊的,因為它們在 JS 值上呼叫成員。

dart
extension type Date(JSObject _) implements JSObject {
  external int getDay();
}

用法部分所述,呼叫getDay()將導致在 JS 物件上呼叫getDay()。因此,透過使用不同的JSObject,可以呼叫getDay的不同實現

為此,應該有一種機制來建立一個具有getDay屬性的 JS 物件,該屬性在呼叫時會呼叫 Dart 函式。一種簡單的方法是建立一個 JS 物件並將getDay屬性設定為轉換後的回撥,例如。

dart
final date = Date(JSObject());
date['getDay'] = (() => 0).toJS;

雖然這可行,但這容易出錯,並且在使用許多互操作成員時擴充套件性不佳。它也無法正確處理 getter 或 setter。相反,你應該結合使用createJSInteropWrapper@JSExport來宣告一個型別,該型別為所有external例項成員提供實現。

模擬示例

#
dart
import 'dart:js_interop';

import 'package:expect/minitest.dart';

// The Dart class must have `@JSExport` on it or at least one of its instance
// members.
@JSExport()
class FakeCounter {
  int value = 0;
  @JSExport('increment')
  void renamedIncrement() {
    value++;
  }
  void decrement() {
    value--;
  }
}

extension type Counter(JSObject _) implements JSObject {
  external int value;
  external void increment();
  void decrement() {
    value -= 2;
  }
}

void main() {
  var fakeCounter = FakeCounter();
  // Returns a JS object whose properties call the relevant instance members in
  // `fakeCounter`.
  var counter = createJSInteropWrapper<FakeCounter>(fakeCounter) as Counter;
  // Calls `FakeCounter.value`.
  expect(counter.value, 0);
  // `FakeCounter.renamedIncrement` is renamed to `increment`, so it gets
  // called.
  counter.increment();
  expect(counter.value, 1);
  expect(fakeCounter.value, 1);
   // Changes in the fake affect the wrapper and vice-versa.
  fakeCounter.value = 0;
  expect(counter.value, 0);
  counter.decrement();
  // Because `Counter.decrement` is non-`external`, we never called
  // `FakeCounter.decrement`.
  expect(counter.value, -2);
}

@JSExport 允許你宣告一個可在createJSInteropWrapper中使用的類。createJSInteropWrapper 將建立一個物件字面量,它將類的每個例項成員名稱(或重新命名)對映到一個 JS 回撥,該回調使用Function.toJS建立。當被呼叫時,JS 回撥將反過來呼叫例項成員。在上面的示例中,獲取和設定counter.value會獲取和設定fakeCounter.value

你可以透過從類中省略註解而只註解特定成員來指定類中的某些成員進行匯出。你可以在@JSExport的文件中檢視關於更專業化匯出(包括繼承)的更多細節。

請注意,此機制並非僅限於測試。你可以使用它為任意 Dart 物件提供一個 JS 介面,從而讓你能夠實質上將 Dart 物件匯出到 JS,並帶有一個預定義的介面。