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)
我对此有几点疑惑:
- 我正处于编译所有内容的阶段,但生成的 javascript 在调用时失败。我相信这是因为我正在传递
PolygonOpt
的实例,而运行时需要一个 javascript 对象文字
Point
的定义是不是翻译成了一个js数组,有两个分量?
- 我希望能够像
def apply(points: Seq[Point], closed: Boolean): Shape
那样重载 Polygon.apply
,但是 scala.js 不允许我在 Polygon
中编写方法实现,因为它扩展了 js.Object
此外,我有一个使用 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 开始也是完全不透明的,这解释了为什么你看不到字段 points
和 closed
.
首先要做的是准确打字,用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
.
我正在尝试为我的图书馆编写一个类型化的外观 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)
我对此有几点疑惑:
- 我正处于编译所有内容的阶段,但生成的 javascript 在调用时失败。我相信这是因为我正在传递
PolygonOpt
的实例,而运行时需要一个 javascript 对象文字 Point
的定义是不是翻译成了一个js数组,有两个分量?- 我希望能够像
def apply(points: Seq[Point], closed: Boolean): Shape
那样重载Polygon.apply
,但是 scala.js 不允许我在Polygon
中编写方法实现,因为它扩展了js.Object
此外,我有一个使用 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 开始也是完全不透明的,这解释了为什么你看不到字段 points
和 closed
.
首先要做的是准确打字,用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
.