一个不可变的追捕,请在JavaScript的安全数据记录

自从与Scala的合作case类我就迷上了具有也是不可改变的类型安全的数据记录的想法。有什么理由不喜欢?这是类型安全不变的(杜)。所以我想看看我是否能得到在JavaScript同样的事情 - 人类已知的最易变的和动态的语言。

{给定的名称;;}

这将作为我们的出发点:一个简单的类在JavaScript。它根据包含两个类的实例字段ES类字段和静态属性ES7提案。此外,它是可变的。实例后被创建,它的领域是可以改变的。

immutable.js

Facebook的JavaScript库一成不变提供了一个不可改变记录数据类型。让我们来看看它能走多远需要我们。

常量=记录({给定的名称“”“”});

这就是我们如何定义我们的纪录。如果你问我,我觉得奇怪,须以定义记录结构提供默认值。它混合两个问题:结构定义和默认值。因此,我们不能要求将提供一个属性;总有一个默认值。

无论如何,这是不错的,我们可以直接访问每个属性,而不是被迫使用类似。得到()

常量=({给定的名称“荷马史诗”“辛普森”});给定的名称//“荷马”

什么是非常令人惊讶的是,建设有任何附加属性记录时没有运行时错误。

常量=({给定的名称“荷马史诗”年龄40});年龄//未定义

我觉得这是相当危险的。也许我们可以把它较少受到加式的安全问题?

键入JavaScript的安全?目前有两个严重的努力,把类型安全的JavaScript:打字稿。从我迄今所看到的,他们的语法几乎是相同的。

我挑选流程为这一点,因为它似乎更容易上手,并与巴别塔效果很好。打字稿可能工作好像很好。

有一个未决问题有适当流量类型定义一成不变但是使用目前的状态已经工作得很好。

常量=({给定的名称42});//错误类型:数与串不相容常量=({给定的名称“荷马史诗”年龄42});//错误类型:属性`age`未找到

这是相当不错的!我们得到错误的数据类型和未知的初始化性能检查。

不幸的是,属性访问似乎并没有得到适当的检查。

年龄//类型检查OK,返回undefined

总而言之,我们可以得到相当远用记录。但它并不是完美的。

巴别塔

似乎我们无法通过编写代码实现我们的目标,我们需要改变的代码。由于JavaScript没有宏要做到这一点,我们就退而求其次:通天插件。

什么是通天?巴别塔出生作为一个transpiler即以现代JavaScript代码和生成代码,可以在其中一些最新的功能不被支持旧的平台上运行。但是,它已经发展成为一个更一般的代码转换和代码生成平台。

该插件需要知道做哪一类不可改变的,这忽略。所以我们创建了一个新的装饰,以纪念为转型类:

@记录(){给定的名称;;}

我们巴贝尔插件会寻找@记录和改造代码到这一点:

@记录(){构造函数在里面{这个__给定的名称=在里面给定的名称;这个__姓=在里面;}__给定的名称;__姓;得到给定的名称(){返回这个__给定的名称;}得到(){返回这个__姓;}}

事情是这样的:

这是我们如何使用它:

常量=({给定的名称“荷马史诗”“辛普森”});安慰日志`创建爸爸$ {给定的名称}$ {}`);//将输出“创建爸爸辛普森”

但它不是非常有用的,但。我们如何改变呢?通过创建一个副本!因为这可能是非常繁琐,容易出错的较大物体,我们产生帮助我们与方法:

@记录(){// ...更新更新{返回({给定的名称更新给定的名称||这个__给定的名称更新||这个__姓});}}

更新得到一个对象,并创建一个新的通过使用所提供的数据或回退到现有的数据,如果没有提供。

现在,我们可以轻松地创建我们的记录的副本:

常量儿子=更新({给定的名称“巴特”});安慰日志`创建儿子$ {儿子给定的名称}$ {儿子}`);//将输出“创建儿子巴特·辛普森”

下一步是使它类型安全的。基本上,我们原来的代码仅接收其字段类型的注释。而已。

@记录(){给定的名称;;}

该插件现在大多只是复制新类型注释到正确的地方,但也创造了两个新类型:

@记录(){构造函数在里面PersonInit{这个__给定的名称=在里面给定的名称;这个__姓=在里面;}__给定的名称;__姓;得到给定的名称(){返回这个__给定的名称;}得到(){返回这个__姓;}更新更新PersonUpdate{返回({给定的名称更新给定的名称||这个__给定的名称更新||这个__姓});}}类型PersonInit={给定的名称;;};类型PersonUpdate={给定的名称;;};

第一个,PersonInit限定用于初始化的记录的对象的类型。第二个,PersonUpdate限定用于创建记录的副本的对象的类型。这是通知重要的是它包含可选的属性(标记为在最后)。这意味着客户端没有指定任何人。

我们现在有一个类型安全,永久记录。

({给定的名称“荷马史诗”“辛普森”});// 好({给定的名称“荷马史诗”});//错误类型:属性`familyName`未找到({给定的名称“荷马史诗”真正});//错误类型:布尔与字符串不兼容({给定的名称“荷马史诗”“辛普森”})。更新({});// 好

不幸的是,类型检查程序提供初始化或更新过程中未知属性时失败。这意味着我们可以很容易有一个可选字段,一个错字,不知为什么没有任何反应。

常量女儿=更新({givnam“丽莎”};// 好

显然,这是在流动的已知的限制。该解决方法密封通过添加一个“全捕获属性”与对象类型空虚类型。

类型PersonInit={给定的名称;;[]空虚;};类型PersonUpdate={给定的名称;;[]空虚;};

现在,当我们使用一个无效的属性键,它失败。

常量女儿=更新({givnam“丽莎”};//错误类型:字符串是未定义不兼容

这固然稍微有点隐蔽的错误消息,现在告诉我们,givnam不是更新数据类型的一部分。

顺便说一句,做此类检查的流程配置文件.flowconfig需要标志unsafe.enable_getters_and_setters =真处理干将。


此代码生成的源代码上找到GitHub上

斯蒂芬·贝肯

通过贸易软件开发人员。大部分时间在不断持续追求简洁,优雅和美丽代码。或者刚刚做的东西在中间。

评论本站由Disqus