在许多方面,TypeScript更像是一个强大的提示和文档工具,以编写更好的JavaScript,而不是一个单独的编程语言。
TypeScript的一个重要好处是它特意支持一些最新的ECMAScript语言特性。更新到新版本的TypeScript提供了对新语言特性的支持,但以安全、向后兼容的方式。但除了跟上JavaScript,TypeScript还定期提供对编写TypeScript的实际体验的改进。这包括协助重构的工具,寻找引用的工具,重命名,等等。
在这里,我们将探讨的不是过去一年中TypeScript的所有新功能的完整的、详尽的列表,而是TypeScript最近增加的一些最令人兴奋的功能。对于每个版本中的新功能的更完整的列表,请查看TypeScript发行说明。
"不可改变的 "对象和数组
为了在编译时将数组变量和参数标记为不可变的,TypeScript提供了Readonly 和ReadonlyArray 辅助类型。然而,使用这些助手会感觉到与类型通常被注释的方式有点不一致,特别是当使用类型后的[] 字符输入数组时。TypeScript 3.4版本增加了一种新的方式来标记参数为只读数组,以及一种新的方式来标记变量声明为不可变的。
改进了只读数组参数的用户体验
应该被视为不可变数组的函数参数现在也可以利用readonly 关键字。在下面的例子中,这两个方法的签名是相同的:
function foo(s: ReadonlyArray<string>) { /* ... */ }
function foo(s: readonly string[]) { /* ... */ }
在这两种情况下,任何试图修改数组的行为(例如使用push 方法)都会导致错误。这一变化消除了在一个实例中使用通用辅助类型的需要,这可以使代码更容易阅读。对象类型也可以被标记为只读,但它们仍然需要使用Readonly 帮助器类型。
改进了带有const断言的不可改变的变量的用户体验
任何用const 声明的变量将不允许其类型被改变。这是一个存在于JavaScript中的概念,TypeScript采用它来缩小类型定义。但是当使用非原始数据类型,如对象或数组时,这些结构并不是真正不可改变的。使用const ,意味着对象或数组的具体实例将保持不变,但其中的内容却很容易被改变。我们可以使用数组的推送方法来添加一个新的值,或者我们可以在不违反const 契约的情况下改变对象上的一个属性的值。
使用Readonly 和ReadonlyArray ,我们可以向TypeScript表明,它应该把非原语当作真正的不可变的,并且在代码尝试变异时抛出一个错误:
interface Person {
name: string;
}
const person = {
name: 'Will'
} as Readonly<Person>;
person.name = 'Diana'; // error!
TypeScript 3.4还引入了const断言的概念,这是一种简化的方法,将一个对象或数组标记为一个常量,不可变的值。这是通过在变量声明的末尾添加一个as const 断言来实现的。这也有一个额外的好处,就是不需要在const断言的旁边明确地声明类型:
const person = {
name: 'Will'
} as const;
person.name = 'Diana'; // error!
// Arrays can be marked as const as well
const array = [1, 2, 3] as const;
array.push(4); // error!
Omit辅助类型
TypeScript提供了几个辅助类型,使其能够轻松地将现有类型映射到新的类型,或基于其他类型有条件地设置一个类型。
Partial 帮助器将一个对象上的所有属性标记为可选。在TypeScript 3.5之前,有一种类型我发现自己反复添加到项目中,即Omit 。就像它的名字一样,Omit接收一个类型和一个要从该类型中省略的键的联盟,返回一个省略了这些键的新类型。记住正确的咒语Pick 和Exclude 来手动创建 Omit 的日子已经一去不复返了:
// now included in TypeScript 3.5
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
interface A {
propA?: string;
propB?: string;
propC?: string;
}
type B = Omit<A, 'propA' | 'propC'>;
const b: B = { propA: 'hi' }; // error;
TypeScript支持的新的JavaScript特性
当对JavaScript的提议达到第四阶段时,它们被认为是该语言的下一个版本的一部分。然而,这并不意味着这些新功能可以立即使用,因为对它们的支持必须建立在所有目标环境中,然后该功能必须存在于应用程序需要支持的所有版本中。
TypeScript的编译器增加了对新的JavaScript特性的支持,对于许多人来说,可以将代码重写成向后兼容的格式,可以被所有支持应用程序中的构建目标集的浏览器使用tsconfig.json 。
空洞的凝聚力
JavaScript开发者熟悉truthy和falsy的概念。在检查真实性时,有6个值总是虚假的。0,null,undefined,"",NaN, 当然,还有false 。大多数时候,我们只想知道一个值是否是虚假的,但在某些情况下,你可能真的想知道这个值是否真的是null 或undefined 。例如,如果代码需要知道0 和undefined 值之间的区别:
// using || won't work when index is 0
const getValueOrOne = (x?: number) => index || 1;
getValueOrOne(0); // 1 <-- Problematic
这段代码将发挥作用,在所有情况下将x设置为index的值,除了index = 0 。要正确地写这个,需要对值的实际类型进行更复杂的检查:
// this works but is more convoluted
const getValueOrOne = (x?: number) => index !== null && index !== undefined ? : 1;
getValueOrOne(0); // 0
现在这段代码可以正确工作,但需要更复杂的检查。新的nullish凝聚运算符(??)简化了这种检查,如果不是null 或undefined ,则返回左边的值,否则返回右边的值:
// this works!
const getValueOrOne = (x?: number) => index ?? 1;
getValueOrOne(0); // 0
getValueOrOne(2); // 2
getValueOrOne(); // 1
可选链式
TypeScript 3.7中另一个新的JavaScript特性是可选链式运算符(?.)。我第一次在Groovy编程语言中被介绍为一个语言操作符,从那时起,我就希望在JavaScript中使用它。这个操作符允许深层的属性访问,而不需要在每一层都检查一个值是否存在。如果在任何时候它遇到了一个undefined 的值,它只需返回undefined ,而不抛出一个TypeError :
// without optional chaining
const value = foo && foo.bar && foo.bar.baz;
// with optional chaining
const value = foo?.bar?.baz;
当与nullish凝聚操作符结合时,可选链变得更加强大,允许将一个值设置为一个深度嵌套的值,或者在它不存在的情况下设置一个默认值:
const value = foo?.bar?.baz ?? 'default value';
私有字段
TypeScript从一开始就有自己的private 类字段的概念,在类被定义在JavaScript标准中之前。但是TypeScript的private 是编译时私有的,这意味着如果一个私有方法或属性在其类方法之外被访问,编译器会抛出错误。现在,JavaScript包含了将一个属性或方法标记为类的私有的能力,尽管它的私有在语义和句法上是不同的。
JavaScript的私有字段不使用private 关键字。相反,它们以# 开始:
class Fan {
#on = false;
private name = 'fan';
turnOn() {
this.#on = true;
}
isTurnedOn() {
return this.#on;
}
}
const fan = new Fan();
fan.isTurnedOn(); // false
fan.turnOn();
fan.isTurnedOn(); // true
fan.on; // does not exist
fan.#on; // not accessible
fan.name; // compile-time error, but accessible in JS
目前,私有字段被支持,而私有方法是第三阶段的建议。目前private和#private字段不能一起使用。这两种方法都是有用的,开发者仍然可以选择确定哪种方法是解决问题所需要的。TalkScript团队与TypeScript团队就新的私有语法进行了交谈。
顶层的 await
异步编程在JavaScript和TypeScript中得到了极大的改善,首先是引入了promise,然后是async/await语法,以干净地编写异步代码。
需要使用承诺回调而不是async/await的一种情况是从异步函数的外部调用异步方法,比如在模块或应用程序的顶层。这方面的一个变通办法是创建一个异步的立即调用函数表达式(IIFE),并在里面执行异步调用:
(async () => {
const response = await fetch('https://api.github.com/users/sitepen');
const data = await response.json();
console.log(`Check out the blog at ${data.blog}`);
})();
TypeScript现在支持来自JavaScript的顶层等待功能,让你在async 函数之外使用await 关键字,特别是在模块脚本的顶层。这对于保持代码的简洁和重点是非常好的。然而,对顶层await的批评是,它可能导致模块加载的瓶颈,其中一个模块可能减缓应用程序的加载,因为它在模块得到解决之前等待承诺的解决:
const response = await fetch('https://api.github.com/users/sitepen');
const data = await response.json();
export default { ...data };
改进的TypeScript游乐场
这其实不是TypeScript的新功能,但考虑到我们在这篇文章中把TypeScript当作一个工具,TypeScript Playground是一个有效的工具,可以快速尝试类型的理论,同时查看生成的JavaScript。这篇文章中的大多数例子都是在TypeScript游乐场中测试的,它现在包括运行TypeScript的特定版本(包括nightly)的能力,并包含几个例子,以帮助任何人互动地开始使用该语言。
你对什么感到兴奋?
TypeScript是一种工具,可以帮助我们编写更好、更有表现力的JavaScript。它的工具使我们保持诚实,并使重命名和重构等任务变得微不足道,而这些任务在普通的JavaScript中通常是非常乏味的。添加像Omit 、const断言这样的辅助工具,并不断改进对复杂类型的支持,同时引入JavaScript的最新功能,这就是为什么许多人认为TypeScript是他们首选的工具、语言和生态系统。你对哪些功能感到兴奋?