Scala.js中JS库的类型外观

时间:2015-02-17 09:39:57

标签: scala scala.js

我正在尝试按照Paths.js为我的库the official guide编写一个类型化的外观。

我希望能够翻译的内容如下:

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)

但我不确定到达这一点需要做些什么。

我所做的是以下内容

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
}

然后,我可以像

一样调用它
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数组?
  • 我希望能够像Polygon.apply一样重载def apply(points: Seq[Point], closed: Boolean): Shape,但scala.js不允许我在Polygon内编写方法实现,因为它扩展了js.Object

此外,我有两个版本的库使用common.js(分为几个文件,每个组件一个),另一个可以用作单个<script>标签,将所有内容置于其中命名空间paths(我现在正在使用它)。

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

1 个答案:

答案 0 :(得分:4)

首先,请务必阅读JS interop doc,包括calling JavaScript guide。我想你已经这样做了,因为你已经有了一些合理的东西。但是,除非明确提到,否则您应特别注意那些表示Scala类型和JavaScript类型完全不相关的部分。

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

  

Point的定义是否已翻译成带有两个组件的js数组?

不,不是。因此,JavaScript完全不可理解。它是opaque

更糟糕的是,PolygonOpt,因为它不会延伸js.Object,因此JavaScript也完全不透明,这解释了为什么您看不到字段pointsclosed

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

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的实例并不容易。有关此内容,请参阅this SO question

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

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

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))
    }
  }
}

隐式扩展是编写扩展js.Object的对象时可用的Scala方法的方法,因为正如您所发现的那样,您实际上无法实现 in 中的方法{{1} }。