原文地址: levelup.gitconnected.com/advanced-ty…
译文地址:github.com/xiao-T/note…
本文版权归原作者所有,翻译仅用于学习。
提升你对 TypeScript 的理解,并学这些高级的技术,可以帮助你掌握该语言并且可以更好的在 React 中使用 TypeScript。
去年冬天,我开始使用 TypeScript,我已经从一个使用 any
的新手逐渐成长为一个习惯使用高级内置类型和自定义类型的老手。通过在 JavaScript 代码添加类型判断,让应用变得更加健壮。这篇文章提供了一些使用高级类型的示例,也展示了如何在 React 应用使用它们。
在这里,我们将会探讨 Record
、Partial
、 Required
、 Pick
和一个自定义Omit
类型。
Record
Typescript 2.1 引入了一个非常有用的内置 type Record
:它可以创建类型 map
,而且,非常适合创建复合型的 interface。为了让变量成为 Record
类型,你需要传入一个字符串作为 key 和一些相关的 type。最简单情况是,你有一个 string
作为值的类型。
const SERVICES: Record<string, string> = {
doorToDoor: "delivery at door",
airDelivery: "flying in",
specialDelivery: "special delivery",
inStore: "in-store pickup",
};
这显得微不足道,但是,它为你日常编码中提供更简单的方式定义类型。一种常见的情况是:当你需要把整个业务整体的 interface 作为键值对保存在字典中时,Record
就非常有用。这个 model 可以表示联系人、事件、用户数据、交通请求、电影票据等,各种集合。在接下来的演示中,我们为 product 创建了一个 model,用户可以添加到购物车:
你会看到编辑器是如何自动帮我们定义对象类型的,同时也会标记出错误提示,这是因为一些必要的属性没有定义:
另外,TypeScript 不允许我们为一些定义好的 type 创建空对象,需要提供相关的属性,但是,这时 Record
就有用处了。
另外,也可以用 string
enum
作为 Record
的 key。例如,我们将会用 ErrorsEnum
来保存访问相关的错误信息:
我们来看看在 Material-UI 中它是如何增强类型的。就如指南中所说,你可以使用 CSS-in-JS 添加自定义的样式,然后,通过
withStyles
HOC 注入。你可以通过一个函数定义样式,函数接受一个名theme
的参数,然后,返回相关样式的className
,还可以为这个函数定义类型:
你应该注意到,因为每个样式对象添加了 as CSSProperties
,所以变得非常麻烦。另外,你就可以使用 Record
带来的好处:定一个带有类型的 styles
函数:
现在,你可以在任何组件中安全的使用它,并且会摆脱明确定义 CSS properties 的束缚。
Partial and Required
Partial
可以让对象中所有的属性变成可选的。在很多情况它可以帮到你,比如,当你需要数据渲染组件时,但是,你知道在组件 mount 时并不会加载数据:
你还也可以用 Partial
来为组件定义默认的 props。
相反,TypeScript v2.8 中引入的 Required
,可以让对象中的所有的属性变成必选的:
Required 的用例之一就是 selectors:有时你想为嵌套对象中的属性创造选择器,并且,你知道在选择器调用时将会定义此属性。你可以为此指定一个类型:
这看起来像是作弊,如果,你从可选属性继承必选属性可能会引起类型错误,因此,要小心使用!
听起来很傻,但是,如果你的代码是自动生成的,并且,你所有的 interface 都是 Partial,UI 中所有的元素都是 Required,这种情况并不稀奇。这时你需要检查所有 undefined 的对象 😨。
Pick and Omit
曾经,你是否想过缩减一连串的类型,因为,你意识到下一个 class 并不需要这么多属性?或许你在重构时遇到这类问题,尝试以一种全新的方式分布系统的每一部分。这里有几种方式可以解决这类问题。
Pick
可以让你在一个已经定义好的 interface 中挑选你需要的 key。
Omit
在 TypeScript 的 lib.d.ts
中并没有预先定义,但是,它可以很容易通过 Pick
和 Exclude
来定义。它可以从一个 interface 中排除掉你不想要的属性。
下面两张图片中,ProductPhotoProps
会包含 Product
所有的属性,name 和 descripition 除外:
其中一个实际示例,就来自我的项目:重构一个有着复杂依赖的庞大表单。有一个
FormProps
它包含了错误类型。重新思考后,对于第一个子组件这些错误类型并不是必要的,但是,第二个组件仍然需要。除了错误类型,我用 Pick 提取了一些属性构建了一个新的 interface,这种方式工作的很好。
当然,有多种方式可以合并类型和定义它们之间的关系。如果,从一开始你就把很大一块东西拆解成很多小块,你或许解决了从对象中排除属性的问题。但是,你会遇到扩展类型的问题。
继承扩展 type/interface
当你需要扩展一个 interface 时,所有的属性在新的 interface 都有效。我们来看看,如何合并多个小的 interface 以便符合的我们的任务需求:
这种方法并不是很方便,因为,你必须更有预见性考虑你的对象。从另一方面来讲,它更加快速和简单,让你设计原型或者构建简单 UI 更加炫酷,就像是把数据选到一个只读块中。
总结
通过一些真实代码,我已经介绍了比较流行的 TypeScript 内置的类型。这只是一个 demo,但是,我知道这所有的类型至少会在一个真实场景中有效😉。我希望这篇文章能帮助到你,并且,鼓励你不要害怕 TypeScript。我用 React-Redux 创建了一个 SPA 的代码仓,在这里你可以找到大多数的演示或者它们的替代方案。
然而,针对静态类型我还想多说一些。通常,当你探索一种新技术或者面对一个开发中的挑战时,你开始解决技术问题,反而忘记了你的目的。静态类型并不是你工作的目的,它只是一个工具。如果,它成为了项目中的核心,这代表着你已经走偏了🚀。记住要在业务和技术之间做好平衡,编码快乐!