[Dart翻译]创建一个Dart-to-Javascript互操作库

811 阅读4分钟

本文由 简悦SimpRead 转码,原文地址 medium.com

在我的游戏中,我一直在努力创建一套库,以方便使用Dar...... 的Cordova插件。

我的游戏中,我一直在努力创建一套,以方便使用来自Dart的Cordova插件。我正在使用新的"package:js "包。这个新库取代了旧的 "dart:js "库。替换后的库更容易使用,但缺少文档。我将详细介绍与Cordova Facebook插件的Javascript互操作的所有必要步骤。

首先要做的是。在你的pubspec.yaml中包含js包,并获得依赖性。

dependencies:
  js: ">=0.6.0 <0.7.0"

接下来,为我们的代码创建一个文件,我把我的文件称为 "facebook.dart"。我知道,这很有创意。

现在是有趣的部分,实际构建代码。

所有的互操作代码都需要使用"@JS() "注解,并且必须有一个库,所以让我们从这个开始。

@JS()
library facebook;

仅仅这样,Webstorm就会抓狂,因为它不知道有@JS注解。通过导入package:js来解决这个问题。导入必须在库声明之后,所以后面的一行就可以了。

@JS()
library facebook;

import 'package:js/js.dart';

很好,警告消失了。接下来是声明实际的接口内容。我决定采用的方法是创建一个与Javascript版本相匹配的类,并使方法和属性成为该类的静态成员。这使得调用接口的代码看起来像。

Facebook.login();

以下是初始结构。

@JS()
library facebook;

import 'package:js/js.dart';

@JS('facebookConnectPlugin')
class Facebook {
   
}

注意,我们再次使用了@JS注解,但这次我们传入了我们想要对话的Javascript对象的名称;在这种情况下,是 "facebookConnectPlugin"。

让我们从插件的文档中添加方法名称。我们稍后会担心它们的参数。

@JS()
library facebook;

import 'package:js/js.dart';

@JS('facebookConnectPlugin')
class Facebook {
   external static login();
   external static logout();
   external static getLoginStatus();
   external static showDialog();
   external static api();
   external static logEvent();
   external static logPurchase();
   external static activateApp();
   external static appInvite();
}

这里重要的是,这些方法被标记为 "外部"。我觉得很烦人的一点是,如果一个类有@JS注解,那么所有的方法都必须是外部的。如果都必须是外部的,为什么要让程序员明确说明呢?

让我们来研究一下方法的参数。这在回调等方面变得有点棘手,但一旦你弄明白了,就不会太糟糕。

@JS('facebookConnectPlugin')
class Facebook {
   external static login(List<String> perms, Function success, Function failure);
   external static logout(Function success, Function failure);
   external static getLoginStatus(Function success, Function failure);
   external static showDialog(DialogOptions options, Function success, Function failure);
   external static api(String requestPath, List<String> perms, Function success, Function failure);
   external static logEvent(String name, Object params, num valueToSum, Function success, Function failure);
   external static logPurchase(num value, String currency, Function success, Function failure);
   external static activateApp(Function success, Function failure);
   external static appInvite(Object options, Function success, Function failure);
}

除了 "DialogOptions",这里没有什么疯狂的东西。其他都是普通的Dart类型参数。那么,DialogOptions是什么情况?好吧,Javascript期待的是一个看起来像这样的对象。

{
    method: "share",
    href: "http://example.com",
    caption: "Such caption, very feed.",
    description: "Much description",
    picture: 'http://example.com/image.png',
    share_feedWeb: true // iOS only
}

你会想到这将被表示为一个Dart地图,但地图不能直接转化为Javascript对象。我不知道为什么,但这是事实。为了绕过这个限制,我们需要创建一个 "匿名 "类。

@JS()
@anonymous
class DialogOptions {
   external String get method;
   external set method(String v);
   
   external String get href;
   external set href(String v);
   
   external String get caption;
   external set caption(String v);
   
   external String get description;
   external set description(String v);
   
   external String get picture;
   external set picture(String v);
   
   external bool get share_feedWeb;
   external set share_feedWeb(bool v);

   external factory DialogOptions({
      String method,
      String href,
      String caption,
      String description,
      String picture,
      bool share_feedWeb: false
   });
}

这个类允许我们像这样调用我们的方法。

Facebook.showDialog(new DialogOptions(method: 'share', href: 'http://example.com'), ...);

JS包将为我们把DialogOptions转换成一个JS对象。知道了这些,那么 "logEvent "和 "appInvite "方法呢?它们都需要一个对象。不幸的是,看起来这两个方法都允许任意的对象作为参数。我不知道如何去做,所以我把它们留作对象。也许有人能给我指出正确的方向。

最后,我们需要使用回调。所有的方法都使用成功和失败的回调。为了在JS回调中使用Dart函数,我们需要用一个特殊的方法来包装它:allowInterop()。这将创建必要的JS,调用我们的Dart。下面是一个使用getLoginStatus的例子。

Facebook.getLoginStatus(allowInterop((var response) {
   print('Success login status: ${response}');
}), allowInterop((var response) {
  print('Login failure: ${response}');
}));

非常重要的一点是:你必须不给任何传递给Javascript的函数设置类型。即使你知道它将是什么,dart2js也会崩溃。我已经提交了一个GitHub上的问题,但它被关闭了,因为Dart 2.0修复了这个问题。(关于Dart 2.0的最新情况,请看最后的说明)

这些回调中的响应对象可以像Dart对象一样使用。对于成功,响应将看起来像。

{
    authResponse: {
        userID: "12345678912345",
        accessToken: "kgkh3g42kh4g23kh4g2kh34g2kg4k2h4gkh3g4k2h4gk23h4gk2h34gk234gk2h34AndSoOn",
        session_Key: true,
        expiresIn: "5183738",
        sig: "..."
    },
    status: "connected"
}

你可以通过在回调中这样做来获得userId。

var userid = response.authResponse.userID;

我们就完成了! 我们已经成功地,至少在很大程度上,创建了一个Dart-to-Javascript互操作库。你可以看到这里的完整文件。我喜欢Dart的互操作的一个好处是,我们可以在这个库上进行扩展。举个例子,我为亚马逊移动广告做了一个库。他们的Cordova插件有一个古怪的界面,但我创建了一个单独的库来使用我的互操作库,使粗糙的地方变得平整。

比较一下加载和显示一个横幅广告的互操作方式。

AmazonMobileAds.loadAndShowFloatingBannerAd(
   allowInterop((var res) {
      print("Successful: ${res.booleanValue}");
   }),
   allowInterop((var res) {
      print("Failure: ${res}");
   }), [ad]
);

到更容易理解的。

bool wasShown = await AmazonAds.loadAndShowFloatingBannerAd(_amazonBannerAd);

我包装了Javascript互操作库,并返回一个Future,而不是使用愚蠢的回调。相反,它允许调用脚本使用async/await

下面是一个包装器的例子。

static Future<bool> loadAndShowFloatingBannerAd(Ad ad) {
 var completer = new Completer();
 AmazonMobileAds.loadAndShowFloatingBannerAd(
  allowInterop((var res) {
   completer.complete(res.booleanValue);
  }),
  allowInterop((var res) {
   completer.completeError(res);
  }), [ad]);
 return completer.future;
}

请查看GitHub上的亚马逊库,了解更多信息。

Dart 2.0 (更新至2019年6月19日)

我提到的关于将类型放在 "allowInterop "函数上的错误似乎在Dart 2.0中得到了修复。我还没有花很多时间去发现其他东西。

我计划写一个 "Dart 2.0 "版本的指南,但它应该大部分仍然有效。如果你发现有些东西不再以同样的方式工作了,请随时给我发信息。


www.deepl.com 翻译