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

424 阅读4分钟

原文地址:medium.com/@thebosz/cr…

原文作者:medium.com/@thebosz

发布时间:2016年7月20日 - 5分钟阅读

随着我的游戏,我一直在努力创建一套库,以方便使用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 Map,但地图不能直接翻译成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也会崩溃。即使你知道它会是什么,dart2js也会崩溃。我已经在GitHub上提交了一个问题,但它被关闭了,因为Dart2.0修复了这个问题。(关于Dart 2.0的更新请看最后的说明)

这些回调中的响应对象可以像Dart对象一样使用。对于成功,response会是这样的。

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

你可以在回调中获取用户ID。

var userid = response.authResponse.userID;

然后我们就完成了 我们成功地创建了一个Dart-to-Javascript的互操作库,至少是大部分。你可以在这里看到完整的文件。我喜欢Dart的interop的一个好处是,我们可以对库进行扩展。举个例子,我为亚马逊移动广告做了一个库。他们的Cordova插件有一个不稳定的接口,但我创建了一个单独的库来使用我的interop库,以平衡粗糙的地方。

比较interop的方式来加载和显示一个横幅广告。

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

到更容易理解。

bool wasShown = await AmazonAds.loadAndShowFloatingBannerAd(_amazonBannerAd);

我封装了Javascript interop库,并返回一个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 (更新于6/19/2019)

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

我打算写一个 "Dart 2.0 "版本的指南,但它应该大部分还是可以用的。如果你发现有什么东西不再以同样的方式工作,请随时给我发消息。