External(绑定任意 JS 库)
external
是 ReScript 的主要特性,用于引入和使用 JavaScript 值。
external
和 let 绑定很像,但是:
=
的右侧并不是一个值;它是你要引用的 JS 值的名字。绑定的类型是强制要求的,因为我们需要知道 JS 值的类型是什么。
只能存在于文件或模块的顶层。
ReScript 中有多种 external
,通过它们携带的属性加以区别,以及增强它们的功能。本页只介绍所有 external
的共通机制。它们的不同属性将在后面的相应章节介绍。几个值得注意的:
@val
,@scope
:绑定到全局 JS 值@module
:从 JS 导入或导出到 JS@send
:绑定到 JS 方法
你可以使用我们的语法查找工具来找到它们。
相关文档:external 装饰器列表。
用法
声明之后,你可以把 external
作为普通值来使用,就像 let 绑定一样。
技巧和诀窍
external
+ ReScript 对象的组合非常适合快速原型开发。检查下面的 JS 输出:
// The type of document is just some random type 'a
// that we won't bother to specify
@val external document: 'a = "document"
// call a method
document["addEventListener"](."mouseup", _event => {
Js.log("clicked!")
})
// get a property
let loc = document["location"]
// set a property
document["location"]["href"] = "rescript-lang.org"
我们将 document
的类型指定为 'a
,它又称占位符类型,是多态的。它可以接受任意值,所以你没有获得多少类型安全(除了在不同调用点的类型推断)。然而,这便于在 ReScript 中快速开始使用一个 JavaScript 库,不需要像 TypeScript 的 DefinitelyTyped
仓库的类型绑定库。
然而,如果你想要类型更严格地绑定到 JavaScript 库,你可以阅读随后的几个互操作文档。
性能 & 输出可读性
在编译过程中,external
的声明会内联到调用者那里,而且会在 JS 输出中完全消失。这意味着当你使用 external
时,你可以确定不会有额外的 JavaScript <-> ReScript 转换开销。
另外,输出中没有额外的 ReScript 运行时,输出的可读性更好。
注意:在接口文件中也要使用
external
和@blabla
属性。否则不会进行内联。
设计决策
ReScript 非常重视与现有代码的互操作。我们的类型系统能提供非常强的保证。但这同时也意味着,如果没有一个足够好的互操作系统,将一个代码库逐步转换到 ReScript 会很困难。幸运的是,我们的互操作功能非常全面,与大多数现有的 JavaScript 代码配合得非常好。
健全的类型系统 + 好用的互操作就意味着,我们在增量代码库的覆盖和转换上获得了传统的渐进类型系统的好处,并且没有传统渐进类型系统的缺点:需要复杂的特性来支持现有模式,分析速度缓慢,类型覆盖收益递减,等等。