使用Typescript派生动作类型

大家好! 我的名字叫Dmitry Novikov,我是Alfa Bank的javascript开发人员,今天我将告诉您有关我们使用Typescript派生Action类型的经验,遇到的问题以及如何解决它们的经验。

这是我关于Alfa JavaScript MeetUp的报告的笔录。 您可以在此处的演示幻灯片中查看代码,并在此处广播mitap的录制。

我们的前端应用程序在大量的React + Redux上运行。 Redux数据流看起来像这样:


有动作创建者-返回动作的函数。 动作归入减速器中,减速器在旧的基础上创建新的一面。 组成部分已签约给聚会,聚会又可以分派新动作-一切重复。

这是动作创建者在代码中的外观:


这只是一个返回动作的函数-一个对象,该对象必须具有类型字符串字段和一些数据(可选)。

这是典型的减速器外观:


这是一个常规的开关盒,它查看动作的类型字段并生成新的侧面。 在上面的示例中,它只是在其中添加操作中的属性值。

如果我们在编写减速器时不小心犯了错误怎么办? 例如,像这样,我们将交换不同动作的属性:


Javascript对我们的行为一无所知,并认为此类代码绝对有效。 但是,它无法按预期工作,我们希望看到此错误。 如果没有Typescript,对我们有什么帮助? 让我们尝试代表我们的行动。


首先,我们将为操作编写“前额”类型-Action1Type和Action2Type。 然后,将它们组合为一种联合类型以用于减速器。 该方法简单明了,但是如果在应用程序的开发过程中操作中的数据发生了变化,该怎么办? 不要每次都手动更改类型。 我们将它们重写如下:


typeof运算符会将操作创建者类型返回给我们,而ReturnType将为我们提供函数返回值的类型-即 动作类型。 结果,结果将与上面的幻灯片相同,但不再是手动的-更改操作时,联合类型的ActionType将自动更新。 哇! 我们将其写在减速器中,并...


立即我们从脚本中得到错误。 而且,错误也不是很清楚-foo操作中没有bar属性,而bar中没有foo ...看来应该是这样吗? 似乎有些混乱。 通常,前额方法无法按预期工作。

但这不是唯一的问题。 想象一下,随着时间的流逝,我们的应用程序将会增长,并且我们将有很多行动。 很多


在这种情况下,我们的普通类型对他们来说会是什么样? 大概是这样的:


而且,如果考虑到将添加和删除操作,则必须手动支持所有这些操作-添加和删除类型。 这也根本不适合我们。 怎么办 让我们从第一个问题开始。



因此,我们有几个动作创建者,它们的常见类型是自动派生的动作类型的并集。 每个动作都有一个type属性,并定义为字符串。 这是问题的根源。 为了将一个动作与另一个动作区分开,我们需要每种类型都是唯一的,并且只接受一个唯一的值。



这种类型称为文字。 文字类型有三种类型-数字,字符串和布尔值。



例如,我们具有onlyNumberOne类型,并且指定该类型的变量只能等于数字1。分配2-并得到一个打字错误。 字符串的工作方式类似-只能将一个特定的字符串值分配给变量。 好吧,布尔值是对还是错,没有歧义。

泛型


如何保存此类型而不将其转换为字符串? 我们将使用泛型。 泛型就是对类型的抽象。 假设我们有一个无用的函数,该函数将输入作为参数,然后不做任何更改就返回它。 如何输入? 写任何东西,因为它可以绝对是任何类型? 但是,如果函数中存在某种逻辑,则可能发生类型转换,例如,数字可以变成字符串,并且任意组合将跳过此操作。 不适合。



泛型将帮助我们摆脱这种情况。 上面的条目表示我们正在传递某个类型T的参数,并且该函数将返回与类型T完全相同的参数。我们不知道它是哪种类型-数字,字符串,布尔值或其他内容-但我们可以保证它将是完全相同的类型。 此选项适合我们。

让我们稍微发展一下泛型的概念。 我们一般不需要处理所有类型,而是处理具体的字符串文字。 为此有一个extends关键字:



符号“ T扩展字符串”表示T是某种类型,它是字符串类型的子集。 值得注意的是,这仅适用于原始类型-如果不是使用字符串而是使用具有特定属性集的对象类型,则相反,这意味着T是该类型的OVER集。

下面是使用带有扩展名和泛型的函数的示例:


  • 字符串类型的参数-函数将返回字符串
  • 类型为文字字符串的参数-函数将返回文字字符串
  • 如果参数看起来不像字符串(例如数字或数组),则脚本将给出错误。


好吧,总的来说,它可行。


我们将函数替换为操作的类型-它返回完全相同的字符串类型,但不再是字符串,而是应为原义字符串。 我们收集联合类型,我们代表减速器-一切都很好。 如果我们犯了一个错误并编写了错误的属性,那么时间脚本将不会给我们两个,而是一个逻辑上可以理解的错误:


让我们进一步深入探讨一下字符串类型。 我们将只使用两个泛型(T和U)编写相同的类型。现在,我们有某种类型的T将依赖于另一种U类型,而不是我们可以使用任何类型-至少字符串,至少数字,至少布尔值。 这是使用包装函数实现的:


最后:描述的问题与github上的问题一样挂了很长时间,最后,在Typescript 3.4版中,开发人员向我们提供了一个解决方案-const断言。 它有两种记录形式:


因此,如果您有新鲜的打字稿,则可以在动作中简单地将其用作const,而文字类型不会变成字符串。 在较旧的版本中,您可以使用上述方法。 事实证明,对于第一个问题,我们现在有多达两个解决方案。 但是第二个仍然存在。



我们仍然有很多不同的动作,尽管事实上我们现在知道如何正确处理它们的类型,但是我们仍然不知道如何将它们自动组合在一起。 我们可以手动编写并集,但是如果删除和添加了动作,我们仍然需要手动删除并在类型中添加它们。 错了


从哪里开始? 假设我们从单个文件中一起导入了动作创建者。 我们想一个个地解决他们,推论他们的行为类型,并将它们归为一种联合类型。 最重要的是,我们希望自动执行此操作,而无需手动编辑类型。


让我们开始探讨动作创建者。 为此,有一个特殊的映射类型描述了键值集合。 这是一个例子:


这将为对象创建一个类型,该对象的键为option1和option2(来自Keys集合),并且值为true或false。 在更一般的版本中,这可以表示为mapOfBool的一种类型-具有某种行键和布尔值的对象。

好啊 但是,我们如何验证它是输入中提供给我们的对象,而不是其他类型? 条件类型是类型世界中的简单三元,将帮助我们解决这个问题。


在此示例中,我们检查:类型T与字符串有共同点吗? 如果是,则返回字符串,否则返回永不。 这种特殊类型总是会向我们返回错误。 字符串文字满足三元条件。 以下是一些示例代码:


如果我们在泛型中指定的内容与字符串不同,则打字稿将给我们一个错误。

我们想出了解决方法和验证方法,它仅用于获取类型并将其合并为并集。 这将有助于我们在打字稿中进行类型推断。 推断通常生活在条件类型中,并且执行以下操作:它遍历所有键值对,尝试推断值类型并将其与其他类型进行比较。 如果值的类型不同,则会将它们组合为一个并集。 正是我们需要的!


好吧,现在仍然需要将所有内容放在一起。

原来是这样的设计:


逻辑大致如下:如果T看起来像是一个具有一些字符串键(动作创建者)的对象,并且它们具有某种类型的值(一个将动作返回给我们的函数),则尝试绕过这些对,推断出这些值的类型。并减少它们的常见类型。 如果出现问题,请抛出一个特殊错误(从不输入)。

乍看之下很难。 实际上,一切都非常简单。 值得关注的是一个有趣的功能-由于每个动作都有一个唯一的类型字段,这些动作的类型不会粘在一起,并且在输出中得到完整的并集类型。 这是代码中的样子:


我们将动作创建者作为动作导入,获取它们的ReturnType(返回值的类型是动作),并使用我们的特殊类型进行收集。 事实证明,这是必需的。


结果如何? 我们从所有操作的文字类型中获得了并集。 添加新操作后,类型将自动更新。 结果,我们得到了完全严格的动作类型,现在我们不能犯错了。 好的,在此过程中,我们了解了泛型,条件类型,映射类型,从不和推断-您可以在此处获得有关这些工具的更多信息。

Source: https://habr.com/ru/post/zh-CN452620/


All Articles