TypeScript 简介
已发表: 2020-10-29我编程的第一门语言是 C 和 Java。 使用这些语言,每次定义变量时都必须指定其类型。 如果你试图给它分配另一个类型的值,编译器会报错:
// main.c int main() { int x = "two"; return 0; } > cc main.c -o main // warning: initialization makes integer from pointer without a cast问题是,由于我缺乏经验,我从编译器收到的许多投诉看起来都晦涩难懂。 这就是为什么最后我将编译器视为限制我创造力的工具,而实际上它应该是一个可以提供帮助的合作伙伴。

在我职业生涯的后期,我开始使用一些没有强类型的编程语言,比如 JavaScript 或 PHP。 我认为它们非常酷:快速制作原型非常容易,无需处理挑剔的编译器。
如您所知,WordPress 所基于的编程语言是 PHP 和 JavaScript。 这意味着您可能习惯于在没有编译器监视的情况下编写插件和主题。 只有你、你的技能和你的创造力。 好吧,像这样的错误:
const user = getUser( userId ); greet( user.name ); // Uncaught TypeError: user is undefined 如果您厌倦了代码中的类似undefined的错误,那么是时候将编译器添加到您的工作流程中了。 让我们来看看 TypeScript 是什么,以及它如何让我们将软件质量提高几个数量级。
什么是打字稿
TypeScript 是一种基于 JavaScript 的编程语言,旨在添加强类型和静态类型。 TypeScript 类型允许我们描述对象和变量的形状,从而产生更好的文档和更健壮的代码。 TypeScript 本身将负责验证我们所做的一切。
按照设计,TypeScript 是 JavaScript 的超集。 这意味着任何用纯 JavaScript 编写的代码,根据定义,也是有效的 TypeScript。 但反之则不然:如果您使用 TypeScript 特定的功能,则生成的代码在您转译之前不是有效的 JavaScript。
TypeScript 的工作原理
要了解 TypeScript 的工作原理,我们将使用它的Playground ,这是一个小型编辑器,我们可以在其中编写 TypeScript 代码并查看编译器告诉我们的内容。
作为 JavaScript 的超集,编写 TypeScript 代码非常容易。 基本上是以下 JavaScript 代码:
let user = "David"; let age = 34; let worksAtNelio = true; console.log( user, age, worksAtNelio );也是 TypeScript 代码。 您可以将其复制并粘贴到 TypeScript Playground 中,您将看到它已编译。 那么它有什么特别之处呢? 它的类型,当然。 在 JavaScript 中,您可以执行以下操作:
let user = "David"; let age = 34; let worksAtNelio = true; user = { name: "Ruth" }; console.log( user, age, worksAtNelio );但这会在 TypeScript 中触发错误。 只需在 Playground 中尝试一下,您将看到以下错误:
Type '{ name: string; }' is not assignable to type 'string'.那是怎么回事?
TypeScript 可以自动推断变量的类型。 这意味着我们不需要明确地告诉它“嘿,这个变量是一个字符串”(或者一个数字,或者一个布尔值,或者其他什么); 相反,它会查看第一次给出的值并据此推断其类型。

在我们的示例中,当我们定义变量user时,我们为其分配了文本字符串"David" ,因此 TypeScript 知道user是(并且应该始终是)一个string 。 问题是稍后我们尝试更改user变量的类型,方法是为其分配一个具有单个属性( name )的对象,其值为字符串"Ruth" 。 显然,这个对象不是string ,所以 TypeScript 抱怨并告诉我们不能执行赋值。
有两种可能的方法来解决这个问题:
// Option 1 let user = "David"; user = "Ruth"; // OK. // Option 2 let user = { name: "David" }; user = { name: "Ruth" };让我们在定义变量的类型时明确
但是类型推断只是为了帮助我们。 如果我们愿意,我们可以显式地告诉 TypeScript 变量的类型:
let user: string = "David"; let age: number = 34; let worksAtNelio: boolean = true; console.log( user, age, worksAtNelio );它呈现与 TypeScript 推断的类型完全相同的结果。 现在这引出了一个问题:如果 TypeScript 可以自动推断类型,我们为什么需要这个功能?
一方面,明确指定类型可以更好地记录我们的代码(在下一节中将变得更加清晰)。 另一方面,它允许我们解决类型推断系统的局限性。 是的,你没看错:在某些情况下推理不是很好,TypeScript 能做的最好的事情就是告诉我们“好吧,我不知道这个变量到底应该是什么; 我想它可以是任何东西!”
考虑以下示例:
const getName = ( person ) => person.name; let david = { name: "David", age: 34 }; console.log( getName( david ) ); // Parameter 'person' implicitly has an 'any' type. 在这种情况下,我们定义了一个getName函数,它接收一个参数并返回一个值。 注意我们在这样一个简单的函数中假设了多少事情:我们期望一个对象( person )至少有一个名为name的属性。 但是我们并不能保证调用这个函数的人会正确地使用它。 但即使他们这样做了,我们仍然不知道这个函数的结果类型是什么。 当然,您可能会假设name将是一个string ,但您之所以知道这一点,是因为您是人类并且您理解该词的含义。 但是看看下面的例子:
getName( "Hola" ); // Error getName( {} ); // Returns undefined getName( { name: "David" } ); // Returns a string getName( { name: true } ); // Returns a boolean每次调用函数,我们都会得到不同的结果! 因此,尽管 TypeScript 一直在关注我们,但我们仍然遇到了经典的 JavaScript 问题:这个函数可以接收任何内容,也可以返回任何内容。


您猜对了,解决方案是显式注释函数。
定义自己的类型
但在此之前,让我们看看另一个额外的 TypeScript 特性:自定义数据类型。 到目前为止,我们几乎所有的示例都使用了基本类型,例如string 、 number 、 boolean等,我认为这些类型很容易理解。 我们还看到了更复杂的数据结构,比如对象:
let david = { name: "David", age: 34 };但是我们并没有过多关注 TypeScript 推断的类型,是吗? 我们所看到的只是由于键入不匹配而导致的无效分配的示例:
let user = "David"; user = { name: "Ruth" }; // Type '{ name: string; }' is not assignable to type 'string'.它以某种方式暗示对象的类型是“ {name:string;} ”,您可以将其理解为“这是一个具有称为字符串类型名称的属性的对象”。 如果这是定义对象类型的方式,那么以下应该是有效的 TypeScript:
let david: { name: string, age: number } = { name: "David", age: 34 };确实如此。 但我相信你会同意这并不方便。 幸运的是,我们可以在 TypeScript 中创建新类型。
一般来说,自定义类型基本上是具有自定义名称的常规 TypeScript 类型:
type Person = { name: string; age: number; }; let david: Person = { name: "David", age: 34 }; 酷吧? 现在我们有了Person类型,我们可以轻松地注释我们的getName函数并清楚地指定输入参数的类型:
const getName = ( person: Person ) => person.name; 而这个简单的更新为 TypeScript 提供了很多信息! 例如,它现在可以推断该函数的结果类型是string ,因为它肯定知道Person对象的name属性也是string :
let david: Person = { name: "David", age: 34 }; let davidName = getName( david ); davidName = 2; // Type 'number' is not assignable to type 'string'.但是,与往常一样,如果您愿意,可以显式注释结果类型:
const getName = ( person: Person ): string => person.name;更多关于 TypeScript 类型的东西……
最后,我想与您分享一些有趣的壮举,如果您在 TypeScript 中定义自己的类型,您可以从中受益。
TypeScript 在为变量赋值时要求很高。 如果您已经明确定义了某个变量的类型,则只能分配与该类型完全匹配的值。 让我们看几个例子:
type Person = { name: string; age: number; }; let david: Person = { name: "David", age: 34 }; // OK let ruth: Person = { name: "Ruth" }; // Property 'age' is missing in type '{ name: string; }' but required in type 'Person' let toni: Person = { name: "Toni", age: 35, gender: "M" }; // Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'. Object literal may only specify known properties, and 'gender' does not exist in type 'Person'. 如您所见, Person变量只接受完全符合Person类型的对象。 如果缺少某些属性( ruth没有age )或属性多于类型中包含的属性( toni有gender ),TypeScript 将抱怨并触发类型不匹配错误。
但是,如果我们谈论的是调用函数,事情就不同了! 函数中的参数类型不是一个确切的要求,但指定参数必须遵守的最小接口。 我知道,我知道,这太抽象了。 让我们看一下下面的例子,我相信这会让事情变得更清楚:
type NamedObject = { name: string; }; const getName = ( obj: NamedObject ): string => obj.name; const ruth = { name: "Ruth" } getName( ruth ); // OK const david = { name: "David", age: 34 }; getName( david ); // OK const toni = { firstName: "Toni" }; getName( toni ); // Argument of type '{ firstName: string; }' is not assignable to parameter of type 'NamedObject'. Property 'name' is missing in type '{ firstName: string; }' but required in type 'NamedObject'. 如您所见,我们将getName函数重新定义为更通用的东西:它现在接受一个NamedObject对象,即必须具有名为string类型name的属性的对象。 使用这个定义,我们可以看到ruth和david如何完美地满足这个要求(它们都有一个name属性),但是toni没有,因为它没有期望属性name 。
结论
TypeScript 是一种通过添加静态类型定义来扩展 JavaScript 的编程语言。 这使我们在定义我们使用的数据时可以更加精确,更重要的是,它可以帮助我们更早地发现错误。
将 TypeScript 集成到开发堆栈中的成本相对较小,可以逐步完成。 由于根据定义,所有 JavaScript 代码都是 TypeScript,因此从 JavaScript 切换到 TypeScript 是自动的——您可以一次添加类型并修饰您的代码。
如果您喜欢这篇文章并想了解更多信息,请分享并在下面的评论部分告诉我。
国王教会国际在 Unsplash 上的特色图片。
