TypeScript⼊门教程-阮⼀峰
从 JavaScript 程序员的⾓度总结思考,循序渐进的理解 TypeScript。
什么是TypeScript?
TypeScript是⼀种添加了类型系统的 JavaScript,适⽤于任何规模的项⽬。
我们都知道JavaScript是⼀种弱类型的语⾔。⽽TypeScript增强了它的类型。
由于JavaScript 是⼀门⾮常灵活的编程语⾔,这将导致:
它没有类型约束,⼀个变量可能初始化时是字符串,过⼀会⼉⼜被赋值为数字。
由于隐式类型转换的存在,有的变量的类型很难在运⾏前就确定。
基于原型的⾯向对象编程,使得原型上的属性或⽅法可以在运⾏时被修改。
函数是 JavaScript 中的⼀等公民[2],可以赋值给变量,也可以当作参数或返回值。
这种灵活性就像⼀把双刃剑,⼀⽅⾯使得 JavaScript 蓬勃发展,⽆所不能,从 2013 年开始就⼀直蝉联最普遍使⽤的编程语⾔排⾏榜冠军;另⼀⽅⾯也使得它的代码质量参差不齐,维护成本⾼,运⾏时错误多。
⽽ TypeScript 的类型系统,在很⼤程度上弥补了 JavaScript 的缺点。它是静态语⾔。
⽽JavaScript是动态语⾔,如下⾯的代码直到运⾏时才会报错:
let foo = 1;
foo.split(' ');
同样是这段代码,在TypeScript下编译时就会报错了。
let foo = 1;
foo.split(' ');
你可能会奇怪,这段 TypeScript 代码看上去和 JavaScript 没有什么区别呀。没错!⼤部分 JavaScript 代码都只需要经过少量的修改(或者完全不⽤修改)就变成 TypeScript 代码,这得益于 TypeScript 强⼤的[类型推论][],即使不去⼿动声明变量foo的类型,也能在变量初始化时⾃动推论出它是⼀个number类型。
完整的 TypeScript 代码是这样的:
let foo: number = 1;
foo.split(' ');
TypeScript 是弱类型:
类型系统按照「是否允许隐式类型转换」来分类,可以分为强类型和弱类型。
以下这段代码不管是在 JavaScript 中还是在 TypeScript 中都是可以正常运⾏的,运⾏时数字1会被隐式类型转换为字符串'1',加号+被识别为字符串拼接,所以打印出结果是字符串'11'。
console.log(1 + '1');
作为对⽐,Python是强类型的。需要进⾏强制类型转换。
这样的类型系统体现了 TypeScript 的核⼼设计理念:在完整保留 JavaScript 运⾏时⾏为的基础上,通过引⼊静态类型系统来提⾼代码的可维护性,减少可能出现的 bug。
TypeScript⾮常适合于⼤型项⽬。在中⼩型项⽬中推⾏ TypeScript 的最⼤障碍就是认为使⽤ TypeScript 需要写额外的代码,降低开发效率。但事实上,由于有[类型推论][],⼤部分类型都不需要⼿动声明了。
TypeScript 还可以和 JavaScript 共存。这意味着如果你有⼀个使⽤JavaScript 开发的旧项⽬,⼜想使⽤ TypeScript 的特性,那么你不需要急着把整个项⽬都迁移到 TypeScript,你可以使⽤ TypeScript 编写新⽂件,然后在后续更迭中逐步迁移旧⽂件。如果⼀些 JavaScript ⽂件的迁移成本太⾼,TypeScript 也提供了⼀个⽅案,可以让你在不修改JavaScript ⽂件的前提下,编写⼀个[类型声明⽂件][],实现旧项⽬的渐进式迁移。
事实上,就算你从来没学习过 TypeScript,你也可能已经在不知不觉中使⽤到了 TypeScript——在 VSCode 编辑器中编写 JavaScript 时,代码补全和接⼝提⽰等功能就是通过 TypeScript Language Service 实现的。
⼀些第三⽅库原⽣⽀持了 TypeScript,在使⽤时就能获得代码补全了,⽐如 Vue 3.0
有⼀些第三⽅库原⽣不⽀持 TypeScript,但是可以通过安装社区维护的类型声明库(⽐如通过运⾏npm install --save-dev @types/react来安装React 的类型声明库)来获得代码补全能⼒——不管是在 JavaScript 项⽬中还是在 TypeScript 中项⽬中都是⽀持的:2016-05:@types/react发布,TypeScript 可以开发 React 应⽤了。
2016-05:@types/node发布,TypeScript 可以开发 Node.js 应⽤了。
2020-09:Vue 发布了 3.0 版本,官⽅⽀持 TypeScript。
安装TypeScript
TypeScript 的命令⾏⼯具安装⽅法如下:
npm install -g typescript
以上命令会在全局环境下安装tsc命令,安装完成之后,我们就可以在任何地⽅执⾏tsc命令了。
编译⼀个 TypeScript ⽂件很简单:
tsc hello.ts
我们约定使⽤ TypeScript 编写的⽂件以.ts为后缀,⽤ TypeScript 编写 React 时,以.tsx为后缀。
Hello TypeScript
我们从⼀个简单的例⼦开始。
将以下代码复制到hello.ts中:
function sayHello(person: string) {
return 'Hello, ' + person;
}
let user = 'Tom';
console.log(sayHello(user));
然后执⾏
tsc hello.ts
这时候会⽣成⼀个编译好的⽂件hello.js:
function sayHello(person) {
return 'Hello, ' + person;
}
var user = 'Tom';
console.log(sayHello(user));
上述例⼦中,我们⽤:指定person参数类型为string。但是编译为 js 之后,并没有什么检查的代码被插⼊进来。
这是因为 TypeScript 只会在编译时对类型进⾏静态检查,如果发现有错误,编译的时候就会报错。⽽在运⾏时,与普通的 JavaScript ⽂件⼀样,不会对类型进⾏检查。
如果我们需要保证运⾏时的参数类型,还是得⼿动对类型进⾏判断:
function sayHello(person: string) {
if (typeof person === 'string') {
return 'Hello, ' + person;
} else {
throw new Error('person is not a string');
}
}
let user = 'Tom';
console.log(sayHello(user));
let是 ES6 中的关键字,和var类似,⽤于定义⼀个局部变量,可以参阅。
下⾯尝试把这段代码编译⼀下:
function sayHello(person: string) {
return 'Hello, ' + person;
}
let user = [0, 1, 2];
console.log(sayHello(user));
编辑器中会提⽰错误,编译的时候也会出错。
但是还是⽣成了 js ⽂件。
这是因为 TypeScript 编译的时候即使报错了,还是会⽣成编译结果,我们仍然可以使⽤这个编译之后的⽂件。
如果要在报错的时候终⽌ js ⽂件的⽣成,可以在tsconfig.json中配置noEmitOnError即可。关于tsconfig.json,请参阅()。
基础
本部分介绍了 TypeScript 中的常⽤类型和⼀些基本概念,旨在让⼤家对 TypeScript 有个初步的理解。具体内容包括:
原始数据类型
JavaScript 的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types)。原始数据类型包括:布尔值、数值、字符串、null、undefined 以及 ES6 中的新类型 Symbol 和 ES10 中的新类型 BigInt。本节主要介绍前五种原始数据类型在 TypeScript 中的应⽤。
布尔值
数值
字符串
空值
Null 和 Undefined
任意值
任意值(Any)⽤来表⽰允许赋值为任意类型。
什么是任意值类型?
如果是⼀个普通类型,在赋值过程中改变类型是不被允许的。但如果是any类型,则允许被赋值为任意类型。
任意值的属性和⽅法:
在任意值上访问任何属性都是允许的。
也允许调⽤任何⽅法。
可以认为,声明⼀个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。
未声明类型的变量
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型。
类型推论
如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出⼀个类型。
什么是类型推论
以下代码虽然没有指定类型,但是会在编译的时候报错:
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
TypeScript 会在没有明确的指定类型的时候推测出⼀个类型,这就是类型推论。
联合类型
联合类型(Union Types)表⽰取值可以为多种类型中的⼀种。
简单的例⼦
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
访问联合类型的属性或⽅法
对象的类型——接⼝
在 TypeScript 中,我们使⽤接⼝(Interfaces)来定义对象的类型。
什么是接⼝
在⾯向对象语⾔中,接⼝(Interfaces)是⼀个很重要的概念,它是对⾏为的抽象,⽽具体如何⾏动需要由类(classes)去实现(implement)。
TypeScript 中的接⼝是⼀个⾮常灵活的概念,除了可⽤于对类的⼀部分⾏为进⾏抽象以外,也常⽤于对「对象的形状(Shape)」进⾏描述。
简单的例⼦
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
上⾯的例⼦中,我们定义了⼀个接⼝Person,接着定义了⼀个变量tom,它的类型是Person。这样,我们就约束了tom的形状必须和接
⼝Person⼀致。
可选属性
有时我们希望不要完全匹配⼀个形状,那么可以⽤可选属性。
任意属性
有时候我们希望⼀个接⼝允许有任意的属性,可以使⽤如下⽅式。
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
只读属性
有时候我们希望对象中的⼀些字段只能在创建的时候被赋值,那么可以⽤ readonly 定义只读属性。
数组的类型
在 TypeScript 中,数组类型有多种定义⽅式,⽐较灵活。
「类型 + ⽅括号」表⽰法
数组泛型
⽤接⼝表⽰数组
接⼝也可以⽤来描述数组:
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
NumberArray表⽰:只要索引的类型是数字时,那么值的类型必须是数字。
虽然接⼝也可以⽤来描述数组,但是我们⼀般不会这么做,因为这种⽅式⽐前两种⽅式复杂多了。
不过有⼀种情况例外,那就是它常⽤来表⽰类数组。
类数组
类数组(Array-like Object)不是数组类型,⽐如arguments:
function sum() {
let args: number[] = arguments;
}
上例中,arguments实际上是⼀个类数组,不能⽤普通的数组的⽅式来描述,⽽应该⽤接⼝:
function sum() {
let args: {
[index: number]: number;
length: number;
types是什么意思callee: Function;
} = arguments;
}
在这个例⼦中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有length和callee两个属性。事实上常⽤的类数组都有⾃⼰的接⼝定义,如IArguments, NodeList, HTMLCollection等:
关于内置对象,可以参考⼀章。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。