记录
记录和 JavaScript 对象很像,但:
默认是不可变的
有固定的字段(不可扩展)
类型声明
记录需要强制性的类型声明:
创建记录
要创建一个 person
记录(上面已经声明):
当你创建新的记录值时,ReScript 试图找到符合值的形状的记录类型声明。因此,这里的 me
值被推断为 person
类型。
通过查找 me
值的上方可以找到该类型。如果类型存在于另一个文件或模块中,你需要明确指出它是哪个文件或模块:
在 me
和 me2
中都可以找到 School
的记录定义。建议使用带有常规类型标注的 me
。
字段访问
使用熟悉的点号:
不可变更新
可以用 ...
展开操作符从旧记录中创建新记录。原始记录不会被修改。
注意: 展开不能向记录值添加新字段,记录的形状是由其类型固定的。
可变更新
记录的字段可以是可变的,这允许你用 =
操作符高效地原地更新这些字段。
在类型声明中没有标记为 mutable
的字段不能被修改。
JavaScript 输出
ReScript 记录编译为直接的 JavaScript 对象;查看上面的各种 JS 输出标签。
可选记录字段
ReScript 在 v10
引入了可选记录字段,这意味着你可以定义在创建记录时可以省略的字段。它看起来像这样:
注意 name
有一个 ?
后缀,这意味着字段本身是 可选的。
创建记录
在创建一个记录时,你可以省略任何可选字段,未设置的可选字段值默认为 None
:
这对模式匹配有影响,我们将很快展开讨论。
不可变更新
通过不可变更新来更新一个可选字段,可以让你设置该字段的值,而不用关心它是否可选。
然而,如果你想将可选字段设置为 option
值,你需要在值前加上 ?
:
你可以使用同样的机制将一个可选字段的值设置为 ?None
,从而消去字段的值。
可选字段的模式匹配
模式匹配是 ReScript 最重要的特性之一,当你处理可选字段时,有两点需要注意。
当直接对值进行匹配时,它是一个 option
。例如:
但是,当把字段作为一般记录结构的一部分进行匹配时,它被视为基本的、非可选的值:
有时你确实想知道这个字段是否被设置了。你可以通过在你的匹配选项前加上 ?
来告知模式匹配引擎这一点,就像这样:
技巧和诀窍
记录类型是通过字段名找到的
对于记录,你不能说“我想让这个函数接受任何记录类型,只要它们有 age
这个字段”。下面的方法不会按照预期工作:
相反,getAge
将推断出参数 entity
必须是 monster
类型,这是与字段 age
最接近的记录类型。下面代码的最后一行会报错:
RESlet kraken = {age: 9999, hasTentacles: true}
let me = {age: 5, name: "Baby ReScript"}
getAge(kraken)
getAge(me) // type error!
类型系统会抱怨 me
是一个 person
,而 getAge
只对 monster
有效。如果你需要这样的能力,请使用 ReScript 对象,参考这里。
记录的可选字段可用于绑定
很多 JavaScript API 往往有庞大的配置对象,如果用记录来建模这些对象可能有点令人讨厌,因为你总是需要在创建记录时指定所有记录字段。
v10
引入可选记录字段就是为了帮助解决这个问题。可选字段让你避免指定所有字段,而让你只指定你关心的字段。对于绑定,和其他具有大型配置对象的 API 来说,这是人体工程学上的重大改进。
设计决策
在阅读了前面几节的约束条件后,如果你是动态语言背景的人,你可能会想,为什么首先要用记录而不是直接用对象?因为记录需要明确的类型化,而且不允许将具有相同字段名的不同记录传递给同一个函数,等等。
事实是,在你的应用程序中,大多数时候你的数据的形状实际上是固定的,如果不是,它表示为变体(接下来介绍) + 记录的组合可能会更好。
由于记录类型是通过找到那个单一的显式类型声明来解决的(我们称之为“名义类型(nominal typing)”),类型错误信息最终会比对等的类型(“结构式类型(structural typing)”,如元组)更好。这使得重构更容易;改变一个记录类型的字段天然允许编译器知道它仍然是同一个记录,只是在某些地方被误用。否则,在结构化类型下,可能会很难分辨是定义的地方还是使用的地方出了问题。