理解 Typescript 中的高级概念
本文由 Aaron Xie 撰写,最初发表于Educative, Inc.。
许多开发者在使用 JavaScript 时,都经历过令人头痛的调试。运行程序,发现新的 bug,然后反复调试。经过数小时的调试,问题终于解决了。对于像 JavaScript 这样无法编译的编程语言来说,这是一个常见问题。
为了解决 JavaScript 的不足,微软创建了 TypeScript。随着越来越多的团队意识到将 TypeScript 纳入其技术栈的好处,越来越多的开发人员需要了解它。
今天,您将学习 TypeScript 中的一些高级概念,以便成为专家。
您将学习:
-
什么是 TypeScript?
-
TypeScript 的优点和局限性
-
严格类型简介
-
TypeScript 和面向对象编程
-
TypeScript 中的类型
-
其他需要学习的主题
-
总结和资源
什么是 TypeScript?
TypeScript由 Microsoft 创建和维护,是 JavaScript 的超集,这意味着所有函数式 JavaScript 代码在 TypeScript 下都有效。该语言可以理解为“用于应用程序规模开发的 JavaScript”,主要有两个重点:
-
向当前 JavaScript 引擎提供未来 JavaScript 引擎的功能
-
为 JavaScript 提供类型系统
TypeScript 的组件通常是语言本身(本质上是 JavaScript 加上附加功能和语法)、将代码转换为 JavaScript 的编译器以及语言服务(在编译器管道末端附近提供类似编辑器的应用程序)。
那么,为什么要使用 TypeScript?
-
类型: TypeScript 提供静态类型,许多大型团队(如微软和谷歌)发现这有助于简化开发流程。
-
面向对象编程: TypeScript 支持面向对象编程概念,如接口、继承、类等。
-
编译:与解释性语言 JavaScript 不同,TypeScript 会为您编译代码并查找编译错误,从而使调试更加容易。
安装 TypeScript
在深入研究 TypeScript 之前,请确保你已经成功安装了 TypeScript。获取 TypeScript 工具的主要方式有两种:通过 npm(Node.js 包管理器)或安装 TypeScript 的 Visual Studio 插件。
新公共管理(NPM):
安装
> npm install -g typescript
编译
> tsc helloworld.ts
如果您没有使用 NPM,您可以通过此链接下载 TypeScript 。
TypeScript 的优点和局限性
打字
JavaScript 是一种动态类型语言,这意味着类型错误只能在运行时发现。对于从事复杂项目的大型团队来说,这可能是一个很大的缺点,因为事先发现代码中的所有错误会容易得多。
TypeScript 提供可选的静态类型,使变量无法更改其类型,并且只能接受某些值。这种类型有助于 TypeScript 编译器发现更多错误,从而减少开发人员编写的错误代码。类型保护通过提高代码的可读性和重构性,为代码创建了更完善的结构。
有兴趣了解 JavaScript 和 TypeScript 之间的更多区别吗?您可以在这里了解更多信息:JavaScript 和 TypeScript 之间有什么区别?
IDE 支持
由于 TypeScript 提供了类型,文本编辑器和集成开发环境 (IDE) 可以为开发人员提供更多有用的信息。这些环境可以提供自动完成、代码导航、错误标记等功能,从而提高团队的工作效率。
一些支持 TypeScript 3 的流行环境:
-
微软 Visual Studio
-
WebStorm
-
Visual Studio 代码
-
原子
-
蚀
浏览器兼容性
浏览器兼容性是 TypeScript 提供的强大功能之一。TypeScript 编译器会转换你的代码,使其与所有现代浏览器兼容。这种兼容性是因为编译器能够将 TypeScript 代码转换为所有设备、平台和浏览器都支持的原生 JS。
虽然使用 TypeScript 有很多优势,但它并非完美的解决方案。提升代码可读性的一个缺点是,你必须编写更多代码,这可能会增加开发时间。与使用原生 JavaScript 相比,它还会增加 TypeScript 文件的大小。
成为一名 TypeScript 开发人员。
Educative 的学习 TypeScript课程允许完全的初学者学习主要概念,从而成为一名成熟的 TypeScript 开发人员。
您已经是 TypeScript 学习者了吗?
通过实践掌握高级概念并提高您的 TS 技能。
严格类型简介
现在我们已经了解了 TypeScript 提供的功能,让我们深入了解一些使 TypeScript 成为强大工具的更高级的概念。
noImplicitAny
根据文档,的定义noImplicitAny
是“对任何隐含类型的表达式和声明引发错误”。
这意味着,只要 TypeScript 可以推断类型,如果你允许 ,就会出现错误noImplicitAny
。这个例子可以通过传递函数参数来看到。
function print(arg) {
send(arg);
}
print("hello");
print(4);
在上面的代码中,函数的有效参数是什么print
?如果你没有为函数参数添加类型,TypeScript 将为该参数分配类型any
,从而关闭类型检查。
对于注重代码安全性的开发人员,可以使用,它会通知他们代码中noImplicityAny
任何可能的类型。让我们看看使用相同的函数会发生什么。any
print
function print(arg) { // Error : someArg has an implicit `any` type
send(arg);
}
要修复错误,您可以注释函数参数。
function print(arg: number) { // Error : someArg has an implicit `any` type
send(arg);
}
但如果您仍然想要类型any
,您可以明确将参数标记为any
。
function print(arg: any) { // Error : someArg has an implicit `any` type
send(arg);
}
unknown
类型unknown
与 类型类似,any
因为所有类型都可以赋值给any
和unknown
类型,但区别在于 类型any
可以赋值给任何其他类型,而unknown
类型不可赋值给任何其他类型。这种区别可能比较容易混淆,我们来看一个例子。
function example1(arg: any) {
const a: str = arg; // no error
const b: num = arg; // no error
}
function example2(arg: unknown) {
const a: str = arg; // 🔴 Type 'unknown' is not assignable to type 'string'.(2322)
const b: num = arg; // 🔴 Type 'unknown' is not assignable to type 'number'.(2322)
}
一个变量arg
被传递给两个函数,其类型可以是string
、number
或其他类型。无论其类型是什么,arg
都会被赋值给any
和unknown
。
但是,与any
类型不同,类型的变量unknown
不能分配给另一种类型,如第 7 行和第 8 行所示。类型any
是双向的,而unknown
是单向的。
unknown
当你不知道传递给函数的值的类型,但又想摆脱这些情况时,类型会很有帮助。any
这提高了代码的安全性,因为any
类型可以传播,使你的代码库更容易出错。
strictNullChecks
在 TypeScript 中,null
和undefined
可以分配给每种类型,这意味着它们属于所有类型的域。
let num: number = 123;
num = null; // Okay
num = undefined; // Okay
通常,这会导致意外错误,因为您可以对值为null
或 的变量调用方法undefined
。
interface Person {
hello(): void;
}
const num: number = undefined;
const str: string = null;
const person: Person = null;
person.hello(); // 🔴 Runtime Error!
在严格空检查模式下,null
和不会自动属于所有类型,因此您不能将它们用于不包含或 的undefined
类型。这样,您可能会在编译时收到一条错误,提示。null
undefined
Object is possibly 'undefined'
Luna
是 的一个实例对象Dog
。
class Dog
{
age: number
breed: string
constructor(age: number, breed: string)
{
this.age = age
this.breed = string
}
getRelativeAge(): number
{
return this.age * 7
}
}
let Luna = new Dog(2, 'Labrador')
此语法相当于在 JavaScript ES5 中使用函数对象。
function Dog(age, breed)
{
this.age = age
this.breed = breed
}
Dog.prototype.getRelativeAge = function() {
return this.age * 7
}
var Spot = new Dog(2, 'Labrador')
遗产
既然你已经了解了如何创建对象,那么学习 TypeScript 中的继承就很重要了。继承允许子类从其父类继承某些属性。
例如,您有Animal
一个作为父类。
class Animal
{
age: number
breed: string
constructor(age: number, breed: string)
{
this.age = age
this.breed = breed
}
makeSound_(sound: string): void
{
console.log(sound)
console.log(sound)
console.log(sound)
}
}
然后,可以创建一个Dog
子类。使用关键字 可以实现基本的继承super
,在子类中将其作为函数使用,调用父类中相应的函数。
class Dog extends Animal
{
playsFetch: boolean constructor(age: number, breed: string, playsFetch: boolean)
{
super(age, breed) // call parent constructor
this.playsFetch = playsFetch
} makeSound(): void
{
super.makeSound_('woof woof')
} getAgeInHumanYears(): number
{
return this.age * 7 // super.age will throw error
}
}
class Cat extends Animal
{
constructor(age: number, breed: string)
{
super(age, breed)
} makeSound(): void
{
super.makeSound_('meow meow')
}
}
接口
接口在 JavaScript(以及 TypeScript)中非常强大,因为它们对运行时没有任何影响。TypeScript 允许你声明变量的结构,这赋予了你更强大的功能。
interface Point {
x: number; y: number;
}
declare var test: Point;
TypeScript 中的接口是开放式的,因此其他作者可以在现有的test
变量声明的基础上进行构建。
interface Point {
x: number; y: number;
}
declare var myPoint: Point;
interface Point {
z: number;
}
var myPoint.z; // Allowed
类还可以通过使用关键字来实现接口,以便它们遵循预定义的对象结构implements
。
interface Point {
x: number; y: number;
}
class MyPoint implements Point {
x: number; y: number; // Same as Point
}
由于这个implements
关键字,界面中的任何更改都会产生编译错误,以便您可以轻松更新代码库。
interface Point {
x: number; y: number;
z: number; // New member
}
class MyPoint implements Point { // ERROR : missing member `z`
x: number; y: number;
}
TypeScript 中的类型
TypeScript 最重要的方面之一是从现有的泛型类型创建自定义类型。
联合类型
通常,您可能希望代码允许多种数据类型。当接受null
或undefined
值时,这种需求尤其明显。联合类型可以解决这个问题,它由注解表示|
。
const hello = (name: string | undefined) => { /* ... */ };
在此示例中,类型name
定义为string | undefined
,这意味着任何类型的变量name
都可以是string
或undefined
。
交叉口类型
交叉类型将多个类型组合成一个,使新类型具备组合后类型的特性。您可以通过关键字来实现extend
,如下所示。
function extend<T, U>(first: T, second: U): T & U {
return { ...first, ...second };
}
const x = extend({ a: "hello" }, { b: 42 });
// x now has both `a` and `b`
const a = x.a;
const b = x.b;
元组类型
与 JavaScript 不同,TypeScript 提供了元组类型,它允许你表达一个具有非统一类型和固定数量元素的数组。以下示例演示了一个元组。
var nameNumber: [string, number];
// Okay
nameNumber = ['Ben', 12345];
// Error
nameNumber = ['Ben', '12345'];
其他需要学习的主题
要成为真正的 TypeScript 大师,还有很多东西需要学习。查看这份清单,了解未来的发展方向。
-
映射类型
-
可区分联合类型
-
装饰器
-
函数类型和返回类型
-
TypeScript 中的函数式编程
-
状态机
-
泛型函数
总结和资源
现在您已经了解了 TypeScript 的一些高级主题,是时候开始探索更多强大的 TypeScript 功能了。查看我们的高级 TypeScript 大师班,掌握这门语言并充分利用 TypeScript 提供的工具。
完成课程后,你将对自己的 TypeScript 技能更加自信,能够编写自己的类型,轻松识别编译后的错误,甚至提升你的 JavaScript 整体知识。课程涵盖的主题包括严格类型、泛型函数、泛型接口、组合类型、常见错误等。
继续阅读有关 TypeScript 的内容
-
TypeScript 教程:学习 TypeScript 的分步指南:按照路线图了解 TypeScript 中的基本概念
-
React 和 TypeScript:了解如何利用 TypeScript 和 React 开发强大的 Web 应用程序。