TypeScript 3.7 如何提高质量
在本文中,我将介绍 TypeScript 3.7 最新发布的几个关键亮点。我将从它们如何影响软件和代码质量的角度来探讨,因为说实话,这对我来说很擅长。
我关注 TypeScript 的发布已经有一段时间了。最近几个版本的发布来来去去,并没有对我的日常生活产生什么重大影响。像 3.7 这样的小版本听起来可能不是什么大事,但这个版本在提升代码质量方面却意义非凡。
虽然 TypeScript 3.7 中包含了令人惊讶的大量内容,但我将专门介绍以下变化:
- 可选链式调用
- 空值合并
- 断言函数
- Declare 关键字
- 未调用函数检查
让我们开始吧。
可选链式调用
可选链接是一种语法形式,用于在某些内容为空或未定义的情况下短路评估。
TypeScript 引入了?.
运算符,可以在对象存在的条件下选择性地调用某些操作。
请看下面新旧做事方式的比较:
// Old Way | |
if (deathRay) { | |
deathRay.FireAt(target); | |
} | |
// New Way | |
deathRay?.FireAt(target); |
// Old Way | |
if (deathRay) { | |
deathRay.FireAt(target); | |
} | |
// New Way | |
deathRay?.FireAt(target); |
在 .NET 世界中,我们已经在 C# 中使用可选链有一段时间了,我是它的忠实粉丝。
我喜欢这一点是:
- 它使语法极其简短,但可读性极强
- 这使得检查空值变得容易
这两点对代码和软件质量都有很大帮助。如果我在审查代码时不被 if 语句块的额外语法分散注意力,我就能专注于真正重要的代码。
同样,如果我是一名开发人员,正在编写一个比实际长度更长的方法,我可能会偷懒,假设某个变量已经检查过是否为空。这听起来很傻,但我自己也曾有过这种冲动,不想跳出流程,往上一行添加空值检查。
能够快速且有条件地检查是否为空将比您最初想象的更有助于提高质量。
空值合并
空值合并是指使用??
运算符来评估可能为空或未定义的事物。
首先,“nullish”这个术语让我发笑,因为它非常适合 JavaScript 概念。
JavaScript 需要这种区别,因为它有一个概念,null
它与 无关,但又与之相关undefined
。当然,区别在于,它null
明确地什么也不是,但undefined
实际上并没有被定义为具有任何值。我想,这就是动态语言的危险吧。
例如,看一下以下空值合并前后的代码片段:
// Old Way | |
var calculator = someCalculator; | |
if (calculator !== null && calculator !== undefined) { | |
calculator = new Calculator(); | |
} | |
// New Way | |
var calculator = someCalculator ?? new Calculator(); |
// Old Way | |
var calculator = someCalculator; | |
if (calculator !== null && calculator !== undefined) { | |
calculator = new Calculator(); | |
} | |
// New Way | |
var calculator = someCalculator ?? new Calculator(); |
如您所见,与?
带有赋值的等效 if 检查的三元 () 运算符相比,使用空值合并非常干净和简单。
我喜欢这一点的原因与我喜欢可选链的原因类似——它可以帮助你将注意力集中在真正重要的代码上。
如果我们作为工程师可以消除代码和语法中的额外噪音,我们就能更容易、更早地发现缺陷。
断言函数
我对断言函数比较犹豫。本质上,这些函数如果调用时没有错误,就向 TypeScript 的内部类型解释代码断言了某些内容。这反过来又允许编译器根据现在已证明的事实捕获更具体的问题。
让我们看一个例子:
function assertIsNumber(input: any): asserts input is number { | |
if (typeof(input) !== 'number') { | |
throw new Error('Oh snap!') | |
} | |
} | |
function getStandardFixedNumberString(input: string | number): string { | |
assertIsNumber(input); // Remove this line and we won't compile | |
return input.toFixed(2).toString(); | |
} |
function assertIsNumber(input: any): asserts input is number { | |
if (typeof(input) !== 'number') { | |
throw new Error('Oh snap!') | |
} | |
} | |
function getStandardFixedNumberString(input: string | number): string { | |
assertIsNumber(input); // Remove this line and we won't compile | |
return input.toFixed(2).toString(); | |
} |
这里我们有一个getStandardFixedNumberString
函数,它接受一个已知的值,该值要么是 a string
,要么是 a number
。由于 atoFixed
不存在于string
类型上,所以这段代码通常是不允许的。
该assertIsNumber
函数定义了一个断言子句,其本质上是说“如果这没有错误,那么该函数断言的内容是正确的,并且可以被你的其余方法理解”。
由于我们断言输入是一个数字,在这种情况下,数字可用的函数变得可用,因此 TypeScript 对我们的toFixed
调用没有任何问题。
所以,这就是我的观点:如果您的方法足够长以至于需要断言函数,那么您应该将它们分成更小的方法。
您可能会争辩说,断言函数是让 TypeScript 执行一些运行时类型检查的一种方式,而不是仅在编译时执行的标准静态检查。
然而,我不认为 TypeScript 通过在运行时强制执行规则而蓬勃发展。在我看来,我们应该在编译时强制执行类型,然后在系统边缘验证外部输入。API 调用和用户输入之类的操作应该进行断言和强制类型转换,而不是在主应用程序代码中进行。
尽管如此,断言函数还是值得考虑和关注的,因为它们将来可能会有其他用途。
Declare 关键字
Declare 让我们将动态类型系统与继承结合起来,从本质上重新声明继承的属性。
看一下以下简单的层次结构:
class Person { | |
// Extra logic here | |
} | |
class AwesomePerson extends Person { | |
public doAwesomeStuff(): void { | |
} | |
} | |
class Theme { | |
users: Person[] = []; | |
// Extra logic here | |
} | |
class DarkTheme extends Theme { | |
declare users: AwesomePerson[]; | |
public doStuff() { | |
for (var user of this.users) { | |
user.doAwesomeStuff(); | |
} | |
} | |
// Extra logic here | |
} |
class Person { | |
// Extra logic here | |
} | |
class AwesomePerson extends Person { | |
public doAwesomeStuff(): void { | |
} | |
} | |
class Theme { | |
users: Person[] = []; | |
// Extra logic here | |
} | |
class DarkTheme extends Theme { | |
declare users: AwesomePerson[]; | |
public doStuff() { | |
for (var user of this.users) { | |
user.doAwesomeStuff(); | |
} | |
} | |
// Extra logic here | |
} |
这里我们有一个DarkTheme
从继承而来的Theme
。Theme
声明了一个实体集合Person
,它本身就是一种抽象。
因为我们知道所有使用黑暗主题的人都很棒,所以我们知道该users
属性也只有AwesomePerson
实体。
借助 TypeScript 3.7,TypeScript 也可以理解这一点。
我们用这个declare
关键字来告诉 TypeScript 对某些事情做出假设,但不会为此假设发出任何具体内容。以前我用它来declare
引用共享网页上加载的外部库之类的东西。
这里我们使用declare
它来指定属性在该上下文中具有与先前定义的不同的类型。
我非常喜欢这个功能。虽然它不像其他语言功能那样常用,但它可以帮助处理复杂层次结构的团队理解其属性,而无需进行类型断言。
未调用函数检查
最后,TypeScript 现在可以捕获我们在使用函数时经常犯的一个错误。请看下面的代码:
class AwesomePerson extends Person { | |
public onlyDoesBoringThings(): boolean { | |
return false; | |
} | |
public doAwesomeStuff(): void {} | |
public doBoringStuff(): void {} | |
} | |
function handlePersonTask(person: AwesomePerson) { | |
if (person.onlyDoesBoringThings) { // oops - no () so we're just checking for non-null | |
person.doBoringStuff(); | |
} else { | |
person.doAwesomeStuff(); | |
} | |
} |
class AwesomePerson extends Person { | |
public onlyDoesBoringThings(): boolean { | |
return false; | |
} | |
public doAwesomeStuff(): void {} | |
public doBoringStuff(): void {} | |
} | |
function handlePersonTask(person: AwesomePerson) { | |
if (person.onlyDoesBoringThings) { // oops - no () so we're just checking for non-null | |
person.doBoringStuff(); | |
} else { | |
person.doAwesomeStuff(); | |
} | |
} |
这里我们本应在第 10 行调用person.onlyDoesBoringThings
,但忘记了 ,()
而是用 null / undefined 来评估函数。该函数已定义,因此true
即使调用它会返回 ,条件也会评估为fasle
。
TypeScript 3.7 可以立即捕获此错误:
This condition will always return true since the function is always defined. Did you mean to call it instead?
这个简单的内置检查应该可以提高您的质量,而不需要额外的步骤,所以我完全赞成。
TypeScript 3.7 的后续步骤
如果您想了解有关这些功能或 TypeScript 的其他改进的更多信息,请查看完整的发行说明。
您可以通过运行 npm 更新到 TypeScript 3.7 npm update -g typescript
。
如果您还没有开始使用 TypeScript,请查看我关于将现有 JavaScript 代码迁移到 TypeScript 的文章。
您觉得这些变化怎么样?您最兴奋的是什么?您是否有令人信服的理由来使用我没想到的断言函数?
TypeScript 3.7 如何帮助提高质量一文首先出现在Kill All Defects上。
鏂囩珷鏉ユ簮锛�https://dev.to/integerman/how-typescript-3-7-helps-quality-33jn