目錄

如何模擬 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。