文档 / 语言手册 / 绑定到全局 JS 值
Edit

绑定到全局 JS 值

首先,确保你要绑定的值不在我们提供的 API 中。

一些 JS 值,例如 setTimeout,存在于全局作用域内。你可以像这样绑定到它们:

ReScriptJS Output
@val external setTimeout: (unit => unit, int) => float = "setTimeout"
@val external clearTimeout: float => unit = "clearTimeout"

(我们已经在 Js.Global 模块中提供了 setTimeoutclearTimeout 等绑定。)

上面的代码会绑定到 JavaScript 的 setTimeout 方法,和与之对应的 clearTimeout 方法。external 的类型标注指定 setTimeout 需要满足:

  • 接受一个参数为 unit 且返回 unit 的函数(它在 JS 端变成一个不接受任何内容且不返回任何内容的函数(指返回 undefined))。

  • 和一个调用回调函数的延时值。

  • 返回一个数字,即 timeout 的 ID。这个数字可能会很大,所以我们将其建模为浮点数,而不是 32 位整数。

技巧和诀窍

以上情况并不理想。想想怎么让 setTimeout 返回的 float 来作为 clearTimeout 的输入。你没办法保证将 setTimeout 的返回值传递给 clearTimeout!据我们所知,有人可能把 Math.random() 传递给后者。

我们正在使用具有出色类型系统的语言!让我们利用一个流行的特性来解决这个问题:抽象类型。

ReScriptJS Output
type timerId
@val external setTimeout: (unit => unit, int) => timerId = "setTimeout"
@val external clearTimeout: timerId => unit = "clearTimeout"

let id = setTimeout(() => Js.log("hello"), 100)
clearTimeout(id)

显然,timerId 是一个只能通过 setTimeout 创建的类型!现在我们可以保证 clearTimeout 收到一个有效的 ID。timerId 是否是数字只是一个实现细节。

由于 external 是内联的,我们最终得到的 JS 输出与手写 JS 一样可读。

全局模块

如果你想要绑定一个全局模块内的值,例如 Math.random,可以将 scope 附加到被 val 标注的 external

ReScriptJS Output
@scope("Math") @val external random: unit => float = "random"
let someNumber = random()

你可以通过传递一个元组给 scope 来绑定一个任意深度的对象:

ReScriptJS Output
@val @scope(("window", "location", "ancestorOrigins"))
external length: int = "length"

这将绑定到 window.location.ancestorOrigins.length

特殊全局值

__filename__DEV__ 这样的全局值可能会不存在;你甚至不能将它们建模为 option,因为仅仅是在 ReScript 中引用它们(然后编译为 JS)就会触发常见的 Uncaught ReferenceError: __filename is not defined 错误,例如在浏览器环境中就会这样。

对于这些麻烦的全局值,ReScript 提供了一种特殊的方法:%external(a_single_identifier)

ReScriptJS Output
switch %external(__DEV__) {
| Some(_) => Js.log("dev mode")
| None => Js.log("production mode")
}

第一行的 typeof 检查不会触发 JS ReferenceError。

另一个例子:

ReScriptJS Output
switch %external(__filename) {
| Some(f) => Js.log(f)
| None => Js.log("non-node environment")
};