支持的类型
在 ReScript 中,有些类型和值不直接映射到 JavaScript 中,当值跨越边界时需要进行转换。本文档概述了 genType 如何转换不同的类型。
Int
ReScript 的值,例如 1, 2, 3 是不变的,因此导出到 JS 的 number 类型。
Float
ReScript 的值,例如 1.0, 2.0, 3.0 是不变的,因此导出到 JS 的 number 类型。
String
ReScript 的值,例如 "a", "b", "c" 是不变的,因此导出到 JS 的 string 类型。
Optionals
ReScript 的 option 类型的值,例如 option(int) 类型,像是 None,Some(0),Some(1),Some(2),被导出到 JS 的 null,undefined,0,1,2。
JS 的值是拆箱后的,null / undefined 被 ReScript 合并了。
因此,option 类型被导出为 JS 类型 null 或 undefined 或 number。
Nullables
ReScript 的可空类型的值,例如 Js.Nullable.t(int),像 Js.Nullable.null,Js.Nullable.undefined,Js.Nullable.return(0),Js.Nullable.return(1),Js.Nullable.return(2),被导出到 JS 分别是 null,undefined,0,1,2。
JS 值遵循相同规则:除非参数类型需要转换,否则不会转换。
Records
ReScript 记录类型的值,例如 {x:int} 像 {x:0},{x:1},{x:2},被导出到 JS 的对象的值 {x:0},{x:1},{x:2}。这需要将运行时表示从数组更改为对象。
因此它们被导出到类型为 {x:number} 的 JS 值中。
因为记录在默认情况下是不可变的,所以它们的字段将被导出为 Flow/TS 中的只读属性类型。可变字段在 ReScript 中由例如 {mutable mutableField: string} 指定。
@genType.as 注解可以用来更改 JS 端一个字段的名称。所以 {[@genType.as "y"] x:int} 被导出为 JS 类型 {y:int}。
如果 ReScript 记录的一个字段有 option 类型,它将被导出到一个可选的 JS 字段。例如,ReScript类型 {x: option(int)} 被导出为 JS 类型 {x?: number}。
对象
ReScript 对象的值,例如 {. "x":int},像 {"x": 0},{"x": 1},{"x": 2},被导出为和 JS 相同的对象值 {x:0},{x:1},{x:2}。这不需要转换。所以它们被导出到类型为 {x:number} 的 JS 值中。
只有当某些字段的类型需要转换时才进行转换。
因为对象在默认情况下是不可变的,所以它们的字段将被导出为 Flow/TS 中的只读属性类型。可变字段在 ReScript 中由 { @set "mutableField": string } 指定。
可以混合对象和 option 类型,例如,ReScript 类型 {. "x":int, "y":option(string)} 被导出到 JS 类型 {x:number, ?y: string},不需要转换,并允许在 ReScript 端进行模式匹配。
对象字段名遵循 ReScript 的 mangling 约定(例如:_type 在 ReScript 中代表 JS 中的 type):
删除后缀 “__”。 否则在前缀 “_“ 后跟着大写字母或关键字时删除前缀 ”_“。
元组
ReScript 元组类型的值。(int, string) 被导出为类型为 [number, string] 的相同 JS 值。这不需要转换,除非其中一种类型的元组项需要转换。
虽然 ReScript 元组的类型是不可变的,但 TS/Flow 中目前还没有成熟的支持,所以它们目前被导出到可变的元组。
变体
普通的变体(有首字母大写的 case,例如 | A | B(int))和多态变体(有一个反引号,例如 | `A | `B(int))以相同的方式表示,所以从 JavaScript 的视角来看没有区别。多态变体不需要首字母大写。
变体可以有未装箱或已装箱表示。当最多有一个 case 有 payload 的情况下,并且该 payload 具有对象类型时,使用未装箱表示;否则,将使用已装箱表示。对象类型包括数组、对象、记录和元组。
没有 payload 的变体本质上是标识符的序列。
例如类型 [@genType] type days = | Monday | Tuesday.
对应的 JS 表示是 "Monday", "Tuesday"
类似地,多态变体类型 [@genType] type days = [ | `Monday | `Tuesday ] 具有相同的 JS 表示。
当变体至少有一个 case 有 payload,如果 payload 是对象类型,例如 [ | Unnamed | Named({. "name": string, "surname": string}) ]。
那么该表示是未被装箱的:JS 值是 "Unnamed" 和 {name: "hello", surname: "world"},多态变体也一样。
注意,这个未被装箱的表示,没有使用带有 payload 的变体 case 的标签 "Named",因为该值与其他无 payload 的 case 的区别在于它的类型:是一个对象。
如果变体有多个 case 附带 payload,或者那唯一的 payload 不是对象类型的,则使用被装箱的表示。
被装箱的表示有形状 {tag: "someTag", value: someValue}
例如,类型 | A | B(int) | C(string) 有例如 "A" 和 {tag: "B", value: 42} 和 {tag: "C", value: "hello"}的值。
多态变体的处理方式类似。注意,多态变体的 payload 总是一元的: `Pair(int,int) 只有一个 (int,int) 类型的 payload。相反,普通变体区分一元 Pair((int,int)) 和二元 Pair(int,int) 的 payload。所有这些情况在 JS 中都表示为 {tag: "Pair", value: [3, 4]},转换函数负责处理不同的 ReScript 表示。
@genType.as 注解可用于在 JS 端修改变体 case 导出的名称。所以像 | [@genType.as "Arenamed"] A 导出 ReScript 值 A 到 JS 值"Arenamed" 布尔/整数/浮点常量可以表示为 | [@genType.as true] True 和 | [@genType.as 20] Twenty 和 | [@genType.as 0.5] Half。多态变体也是如此。
@genType.as 注解也可以用在带有 payload 的变体上,以确定出现在 { tag: ... } 中的值。
更多示例,请看 Variants.res 和 VariantsWithPayload.res。
注意: 当导出/导入具有多态变体类型的值时,必须标记类型,不能依赖类型推断。所以不要用 let monday = `Monday,而要用 let monday : days = `Monday。前者不会生效,因为类型检查器推断出没有标记出类型。
数组
带有 t 类型元素的数组被导出到带有相应 JS 类型元素的 JS 数组中。如果需要转换,数组会被拷贝。
有额外的 ReScript 库支持不可变数组 ImmutableArray.res/.resi,需要手动添加到项目中。
类型 ImmutableArray.t(+'a) 是协变的,并且被映射到 TS/Flow 中的只读数组类型。相对于 TS/Flow, ImmutableArray.t 不允许对普通数组进行任意方向的类型转换。相反,拷贝必须使用 fromArray 和 toArray 来执行。
函数和函数组件
ReScript 函数被导出为对应类型的 JS 函数。
例如,一个 ReScript 函数 foo : int => int 导出为接收 number 并返回 number 的 JS 函数。
如果命名参数在 ReScript 类型中出现,它们将被分组并导出为 JS 对象。例如 foo : (~x:int, ~y:int) => int 导出为接收类型为 {x:number, y:number} 的对象并返回 number 的 JS 函数。
在混合命名参数和未命名参数的情况下,连续的命名参数组成独立的组。所以如 foo : (int, ~x:int, ~y:int, int, ~z:int) => int 被导出到类型为 (number, {x:number, y:number}, number, {z:number}) => number 的 JS 函数。
函数组件的导出和导入与普通函数完全相同。例如:
RE[@genType]
[@react.component]
let make = (~name) => React.string(name);
命名参数也遵守 ReScript 的 mangling 约束:
删除后缀 “__”。 否则在前缀 “_“ 后跟着大写字母或关键字时删除前缀 ”_“。
例如:
RES@genType
let exampleFunction = (~_type) => "type: " ++ _type
导入类型
可以在 ReScript 中将现有的 TS/Flow 类型作为不透明类型导入。例如
RES@genType.import("./SomeFlowTypes") type weekday
定义一个映射到 SomeFlowTypes.js 中的 weekday 的类型。
看这个例子 Types.res 和 SomeFlowTypes.js。
递归类型
不需要转换的递归类型被完全支持。 如果递归类型需要转换,则只执行浅转换,并在输出中包含警告注释。(替代方案是对任意大小的数据结构执行昂贵的转换)。 看这个例子 Types.res。
一等公民模块
ReScript 一等公民模块从它们的 ReScript 运行时表示,即数组,转换为 JS 对象类型。 例如,
RESmodule type MT = {
let x: int
let y: string
}
module M = {
let y = "abc"
let x = 42
}
export firstClassModule: module(MT) = module(M)
被导出为JS对象类型
RES{"x": number, "y": string}
注意导出的 JS 对象中元素的顺序是由模块类型 MT 而不是模块实现 M 决定的。
多态类型
如果 ReScript 类型包含类型变量,则不转换对应的值。换句话说,转换就是恒等函数。例如,类型为 {payload: 'a} => 'a 的 ReScript 函数,必须将有效 payload 的值视为黑箱,作为参数多态的结果。如果使用带类型的后端,则将 ReScript 类型转换为相应的泛型类型。
从具有隐藏类型变量的多态类型导出值
对于需要转换包含隐藏类型变量的值的情况,可以使用函数来产生适当的输出:
无效
RES@genType
let none = None
JSexport const none: ?T1 = OptionBS.none; // Errors out as T1 is not defined
有效
RES@genType
let none = () => None
JSconst none = <T1>(a: T1): ?T1 => OptionBS.none;
Promises
类型 Js.Promise.t(arg) 的值被导出为类型 Promise<argJS> ,其中 argJS 是对应于 arg 的 JS 类型。
如果需要对参数进行转换,转换函数通过 .then(promise => ...) 串联起来。