文档 / GenType / 支持的类型
Edit

支持的类型

在 ReScript 中,有些类型和值不直接映射到 JavaScript 中,当值跨越边界时需要进行转换。本文档概述了 genType 如何转换不同的类型。

Int

ReScript 的值,例如 1, 2, 3 是不变的,因此导出到 JS 的 number 类型。

Float

ReScript 的值,例如 1.02.03.0 是不变的,因此导出到 JS 的 number 类型。

String

ReScript 的值,例如 "a""b""c" 是不变的,因此导出到 JS 的 string 类型。

Optionals

ReScript 的 option 类型的值,例如 option(int) 类型,像是 NoneSome(0)Some(1)Some(2),被导出到 JS 的 nullundefined012

JS 的值是拆箱后的,null / undefined 被 ReScript 合并了。

因此,option 类型被导出为 JS 类型 nullundefinednumber

Nullables

ReScript 的可空类型的值,例如 Js.Nullable.t(int),像 Js.Nullable.nullJs.Nullable.undefinedJs.Nullable.return(0)Js.Nullable.return(1)Js.Nullable.return(2),被导出到 JS 分别是 nullundefined012

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.resVariantsWithPayload.res

注意: 当导出/导入具有多态变体类型的值时,必须标记类型,不能依赖类型推断。所以不要用 let monday = `Monday,而要用 let monday : days = `Monday。前者不会生效,因为类型检查器推断出没有标记出类型。

数组

带有 t 类型元素的数组被导出到带有相应 JS 类型元素的 JS 数组中。如果需要转换,数组会被拷贝。

有额外的 ReScript 库支持不可变数组 ImmutableArray.res/.resi,需要手动添加到项目中。

类型 ImmutableArray.t(+'a) 是协变的,并且被映射到 TS/Flow 中的只读数组类型。相对于 TS/Flow, ImmutableArray.t 不允许对普通数组进行任意方向的类型转换。相反,拷贝必须使用 fromArraytoArray 来执行。

函数和函数组件

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.resSomeFlowTypes.js

递归类型

不需要转换的递归类型被完全支持。 如果递归类型需要转换,则只执行浅转换,并在输出中包含警告注释。(替代方案是对任意大小的数据结构执行昂贵的转换)。 看这个例子 Types.res

一等公民模块

ReScript 一等公民模块从它们的 ReScript 运行时表示,即数组,转换为 JS 对象类型。 例如,

RES
module 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
JS
export const none: ?T1 = OptionBS.none; // Errors out as T1 is not defined

有效

RES
@genType let none = () => None
JS
const none = <T1>(a: T1): ?T1 => OptionBS.none;

Promises

类型 Js.Promise.t(arg) 的值被导出为类型 Promise<argJS> ,其中 argJS 是对应于 arg 的 JS 类型。

如果需要对参数进行转换,转换函数通过 .then(promise => ...) 串联起来。