Scala.js 中 JS 库的类型化外观

Typed façade for JS library in Scala.js

我正在尝试为我的图书馆编写一个类型化的外观 Paths.js, following 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) 

我对此有几点疑惑:

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

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

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

所以,一个Int是一个合适的JS number(在一个int范围内)。但是 Array[Point] 与 JavaScript 数组无关。一个Tuple2(比如(1, 3))就更不用说了。所以:

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

不,不是。这样一来,JavaScript就完全无法理解了。是 .

更糟糕的是,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 风格 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))
    }
  }
}

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