Scala.js 中 JS 库的类型化外观 [英] Typed façade for JS library in Scala.js

查看:42
本文介绍了Scala.js 中 JS 库的类型化外观的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为我的库Paths.js编写一个类型化的外观,遵循官方指南.

I am trying to write a typed façade for my library Paths.js, following the official guide.

我希望能够翻译如下调用:

What I would like to be able to translate is a call like the following:

var Polygon = require('paths-js/Polygon');
var polygon = Polygon({
  points: [[1, 3], [2, 5], [3, 4], [2, 0]],
  closed: true
});

进入

val polygon = Polygon(points = List((1, 3), (2, 5), (5, 6)), closed = true)

但我不确定我需要做什么才能达到这一点.

but I am not sure what I need to do to get to this point.

我所做的如下

type Point = (Number, Number)
trait PolygonOpt {
  val points: Array[Point]
  val closed: Boolean
}
@JSName("paths.Polygon")
object Polygon extends js.Object {
  def apply(options: PolygonOpt): Shape = js.native
}

然后,我可以像这样调用它

Then, I can invoke it like

class Opt extends PolygonOpt {
  val points: Array[Point] = Array((1, 2), (3, 4), (5, 6))
  val closed = true
}
val opts = new Opt
val poly = Polygon(opts) 

我对此有一些疑问:

  • 我正在编译所有内容,但生成的 javascript 在调用时失败.我相信这是因为我正在传递一个 PolygonOpt 的实例,而运行时需要一个 javascript 对象文字
  • Point 的定义是不是被翻译成两个组件的js数组?
  • 我希望能够像 def apply(points: Seq[Point], closed: Boolean): Shape 一样重载 Polygon.apply,但是 scala.js不允许我在 Polygon 中编写方法实现,因为它扩展了 js.Object
  • I am at a point where everything compiles, but the resulting javascript fails at the point of invocation. I believe that this is because I am passing an instance of PolygonOpt, while the runtime expects a javascript object literal
  • is the definition of Point translated into a js array with two components?
  • I would like to be able to overload Polygon.apply like def apply(points: Seq[Point], closed: Boolean): Shape, but scala.js does not let me write method implementation inside Polygon since it extends js.Object

此外,我有一个使用 common.js 的库版本(它被分成几个文件,一个用于每个组件)和另一个可以用作单个 <script> 标记,将所有内容都放在名称空间 paths(这是我现在正在使用的那个)下.

Moreover, I have both a version of my library using common.js (which is split into several files, one for each component) and another one which can be used as a single <script> tag, putting everything under the namespace paths (which is the one I am using right now).

哪个更适合 Scala.js 包装器?

Which one works better for a Scala.js wrapper?

推荐答案

首先,请务必阅读 JS 互操作文档,包括调用 JavaScript指南.我认为你已经这样做了,因为你已经有了一些合理的东西.但是你应该特别注意那些说 Scala 类型和 JavaScript 类型完全无关的部分,除非明确提到.

First, make sure to read the JS interop doc, including the calling JavaScript guide. I think you did so already, because you already have something reasonable. But you should pay particular attention to the parts that say that Scala types and JavaScript types are completely unrelated unless explicitly mentioned.

因此,Int 是一个合适的 JS number(在 int 范围内).但是 Array[Point] 与 JavaScript 数组无关.Tuple2(例如 (1, 3))更是如此.所以:

So, an Int is a proper JS number (in the range of an int). But an Array[Point] has nothing to do with a JavaScript array. A Tuple2 (such as (1, 3)) even less so. So:

Point的定义是不是翻译成两个组件的js数组?

Is the definition of Point translated into a js array with two components?

不,不是.因此,它对 JavaScript 来说是完全不可理解的.它是不透明.

No, it is not. As such, it is completely incomprehensible to JavaScript. It is opaque.

更糟糕的是,一个 PolygonOpt,因为它没有扩展 js.Object,所以它对 JavaScript 也是完全不透明的,这就解释了为什么你看不到字段 关闭.

Worse, a PolygonOpt, since it does not extend js.Object, is completely opaque as well from JavaScript, which explains why you cannot see the fields points and closed.

要做的第一件事是使用 JavaScript 可理解的类型(扩展 js.Object)准确键入您的 JS API.在这种情况下,它看起来像这样:

The first thing to do is to type accurately, with JavaScript-understandable types (extending js.Object), your JS API. In this case, it would look like this:

type JSPoint = js.Array[Int] // or Double

trait PolygonOpts extends js.Object {
  val points: js.Array[JSPoint] = js.native
  val closed: Boolean = js.native
}

@JSName("paths.Polygon")
object Polygon extends js.Object {
  def apply(options: PolygonOpt): Shape = js.native
}

现在,要创建一个PolygonOpts的实例并不容易.为此,请参阅 此 SO问题:

Now, the thing is that it is not really easy to create an instance of PolygonOpts. For this refer to this SO question:

object PolygonOpts {
  def apply(points: js.Array[JSPoint], closed: Boolean): PolygonOpts = {
    js.Dynamic.literal(
        points = points,
        closed = closed
    ).asInstanceOf[PolygonOpts]
  }
}

最后,您可以使用隐式扩展公开您想要的 Scala 式 API:

Finally, you can expose the Scala-esque API you wanted in the first place with implicit extensions:

import js.JSConverters._

object PolygonImplicits {
  implicit class PolygonObjOps(val self: Polygon.type) extends AnyVal {
    def apply(points: List[(Int, Int)], closed: Boolean): Shape = {
      val jsPoints =
        for ((x, y) <- points.toJSArray)
          yield js.Array(x, y)
      Polygon(PolygonOpts(jsPoints, closed))
    }
  }
}

隐式扩展是编写 Scala 方法的方法,这些方法似乎可用于扩展 js.Object 的对象,因为正如您发现的,您实际上无法在 内部实现方法一个 js.Object.

Implicit extensions are the way to write Scala methods that appear to be available on objects extending js.Object, since, as you discovered, you cannot actually implement methods inside a js.Object.

这篇关于Scala.js 中 JS 库的类型化外观的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆