使用TypeScript的超详细指南

145 阅读7分钟

2012年底,微软推出了TypeScript,这是一种类型化的JavaScript超集,可以编译成普通的JavaScript。TypeScript最近越来越受欢迎,因为它为JavaScript增加了增强的功能,也被用于Angular等JavaScript框架中。

TypeScript改善了开发者的体验,允许创建高度可扩展的大型应用程序,并允许在开发过程中轻松跟踪错误。它是一种开源语言,专注于实现面向对象的功能,如类、类型注释、继承、模块等等。

在本教程中,我们将开始使用TypeScript,使用简单的小规模代码示例,将其编译为JavaScript,并在浏览器中查看即时结果。


TypeScript设置

要为你的项目设置TypeScript,你只需要一个文本编辑器、一个浏览器和TypeScript包来使用TypeScript。遵循这些安装说明:

		$ npm install -g typescript

如果你在Mac OS或Linux上安装包时收到权限错误,你可以运行这个命令:

sudo npm install -g typescript
  • 要检查TypeScript的版本,在终端键入这个命令:
tsc -v

就这样--我们现在已经准备好在TypeScript中制作一个简单的Hello World应用程序了!


TypeScript中的Hello World

TypeScript是ECMAScript 5 (ES5)的超集,并结合了ES6的特性。正因为如此,任何JavaScript程序都已经是一个TypeScript程序。TypeScript编译器对TypeScript程序进行本地文件转换。因此,最终的JavaScript输出与TypeScript的输入密切相关。

首先,让我们在文本编辑器中创建一个hello-world文件夹,然后我们将在这个文件夹中创建一个基本的index.html文件并引用一个外部脚本文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Learning TypeScript</title>
</head>
<body>
    <script src="hello.js"></script>
</body>
</html>

这是一个简单的 "Hello World "应用程序,所以让我们创建一个名为hello.ts的文件。*.ts的扩展名指定为TypeScript文件。在hello.ts中添加以下代码:

alert('hello world in TypeScript!');

接下来,打开命令行界面,导航到包含hello.ts的文件夹,并通过以下命令执行TypeScript编译器:

tsc hello.ts

tsc 命令是TypeScript编译器,它立即生成了一个名为hello.js的新文件。我们的TypeScript应用程序没有使用任何TypeScript特定的语法,所以我们在hello.js中看到的JavaScript代码和我们在hello.ts中写的一样。

很好!现在我们可以探索TypeScript的功能,看看它如何帮助我们维护和编写大型JavaScript应用程序。

hello world in typescript


配置TypeScript编译器

.ts文件最初是由TypeScript编译器编译成ES5的。然而,由于TypeScript也支持ES6语法,这是更新的语法,我们将学习如何配置TS编译器来针对其他JavaScript版本,如ES6。

在终端中,运行:

tsc --init

这个命令会生成一个名为tsconfig.json的配置文件。这个文件由很多设置组成,其中一个是targettarget ,指定TS编译器将生成的JavaScript文件的版本。

现在,打开tsconfig.json文件,将targetes5 改为es6 ,以确保我们对所有浏览器都有最安全的选择。

类型注释

类型注释是一个可选的功能,它允许我们在我们编写的程序中检查和表达我们的意图。让我们在一个新的TypeScript文件中创建一个简单的area() 函数,名为type.ts

function area(shape: string, width: number, height: number) {
	let area = width * height;
	return "I'm a " + shape + " with an area of " + area + " cm squared.";
}

document.body.innerHTML = area("rectangle", 30, 15);

接下来,将index.html中的脚本源改为type.js,然后用tsc type.ts 运行TypeScript编译器。在浏览器中刷新页面,你应该看到下面的情况:

app output

如前面的代码所示,类型注释被表达为函数参数的一部分;它们表明你可以向函数传递什么类型的值。例如,shape 参数被指定为一个字符串值,widthheight 是数字值。

类型注释和其他TypeScript特性,只在编译时强制执行。如果你向这些参数传递任何其他类型的值,编译器会给你一个编译时错误。这种行为在构建大规模应用程序时是非常有帮助的。例如,让我们故意为width 参数传递一个字符串值:

function area(shape: string, width: number, height: number) {
	let area = width * height;
	return "I'm a " + shape + " with an area of " + area + " cm squared.";
}

document.body.innerHTML = area("rectangle", "width", 15); // wrong width type

我们知道这将导致一个不理想的结果,而编译文件会以下面的错误提醒我们注意这个问题:

$ tsc type.ts
type.ts:6:45 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

注意,尽管有这个错误,编译器还是生成了type.js 文件。这个错误并没有阻止TypeScript编译器生成相应的JavaScript,但编译器确实警告我们有潜在的问题。我们打算将width 作为一个数字;传递其他任何东西都会导致我们的代码中出现不希望出现的行为。其他原始类型包括bool ,甚至是any ,在试图防止某个特定变量的类型错误时使用。

此外,TypeScript在其功能中添加了一些额外的类型注释。

图元

图ples是固定长度的数组,其中每个元素都有一个特定的类型:

let shapeType: [string, number] = ["Triangle", 100];

看一下上面的代码,数组有一个固定长度的两个元素,代表一个形状和面积的名称;第一个元素的类型是string ,第二个元素的类型是number 。如果我们尝试添加第三个元素,例如一个数字类型,我们会得到一个错误。

type.ts:47:5 - error TS2322: Type '[string, number, number]' is not assignable to type '[string, number]'.
  Source has 3 element(s) but target allows only 2.

枚举

Enum持有相关的常数标识符或值的组。它们是用enum 关键字定义的 让我们看看下面的代码示例:

enum ShapeType {
  Triangle = "TRIANGLE",
  Circle = "CIRCLE",
  Cube = "CUBE",
}

// Access the enums
ShapeType.Cube; //returns CUBE

上面的代码是一个字符串枚举类型的例子。还有一种数字枚举类型,它将字符串值存储为数字。

接口

让我们扩展我们的例子,包括一个接口,进一步将一个形状描述为一个具有可选的color 属性的对象。创建一个名为interface.ts的新文件,并修改index.html中的脚本源以包括interface.js。在interface.ts中键入以下代码:

interface Shape {
    name: string;
	width: number;
	height: number;
	color?: string;
}

function area(shape : Shape) {
	let area = shape.width * shape.height;
	return "I'm " + shape.name + " with area " + area + " cm squared";
}

console.log( area( {name: "rectangle", width: 30, height: 15} ) );
console.log( area( {name: "square", width: 30, height: 30, color: "blue"} ) );

接口是给对象类型的名称。我们不仅可以声明一个接口,而且还可以把它作为一个类型注解。

编译interface.ts的结果是没有错误。为了唤起一个错误,让我们在interface.ts中附加另一行代码,用一个没有名字属性的形状,在浏览器的控制台中查看结果。将这一行追加到interface.ts

console.log( area( {width: 30, height: 15} ) );

现在,用tsc interface.ts 来编译代码。你会收到一个错误,但现在不用担心。刷新你的浏览器,看一下控制台。你会看到与下面的截图类似的东西:

app screenshot

现在我们来看看这个错误它是:

interface.ts:15:20 - error TS2345: Argument of type '{ width: number; height: number; }' is not assignable to parameter of type 'Shape'.
  Property 'name' is missing in type '{ width: number; height: number; }' but required in type 'Shape'.

我们看到这个错误是因为传递给area() 的对象不符合Shape 接口;它需要一个名称属性才行。


箭头函数表达式

理解this 关键字的范围是具有挑战性的,TypeScript通过支持箭头函数表达式使其变得简单一些,这是ECMAScript 6中支持的功能。箭头函数保留了this 的值,使得编写和使用回调函数更加容易。考虑一下下面的代码:

const shape = {
	name: "rectangle",
	popup: function() {

		console.log('This inside popup(): ' + this.name);

		setTimeout(function() {
			console.log('This inside setTimeout(): ' + this.name);
			console.log("I'm a " + this.name + "!");
		}, 3000);

	}
};

shape.popup();

第7行的this.name ,显然会是空的,这在浏览器的控制台中有所体现:

empty name

我们可以通过使用TypeScript的箭头函数轻松解决这个问题。只需将function() 替换为() =>

const shape = {
	name: "rectangle",
	popup: function() {

		console.log('This inside popup(): ' + this.name);

		setTimeout(() => {
			console.log('This inside setTimeout(): ' + this.name);
			console.log("I'm a " + this.name + "!");
		}, 3000);

	}
};

shape.popup();

结果是什么呢?

name filled in

看一下生成的JavaScript文件。你会看到,编译器注入了一个新的变量,var _this = this; ,并在setTimeout() 的回调函数中使用它来引用name 属性。


具有公共和私有可访问性修饰符的类

TypeScript支持类,其实现与ECMAScript 6密切相关。让我们创建另一个文件,称为class.ts,并审查类的语法:

class Shape {

	area: number;
	color: string;

	constructor ( name: string, width: number, height: number ) {
		this.area = width * height;
		this.color = "pink";
	};

	shoutout() {
		return "I'm " + this.color + " " + this.name +  " with an area of " + this.area + " cm squared.";
	}
}

const square = new Shape("square", 30, 30);

console.log( square.shoutout() );
console.log( 'Area of Shape: ' + square.area );
console.log( 'Name of Shape: ' + square.name );
console.log( 'Color of Shape: ' + square.color );
console.log( 'Width of Shape: ' + square.width );
console.log( 'Height of Shape: ' + square.height );

上面的Shape 类有两个属性,areacolor ,一个构造函数(恰当地命名为constructor() ),以及一个shoutout() 方法。构造函数参数的范围 (name,width, 和height) 是构造函数的局部。这就是为什么你会在浏览器和编译器中看到错误:

class.ts(12,42): The property 'name' does not exist on value of type 'Shape'
class.ts(20,40): The property 'name' does not exist on value of type 'Shape'
class.ts(22,41): The property 'width' does not exist on value of type 'Shape'
class.ts(23,42): The property 'height' does not exist on value of type 'Shape'

errors

接下来,让我们探讨一下publicprivate 的可访问性修改器。公有成员可以在任何地方被访问,而私有成员只能在类主体的范围内被访问。当然,在JavaScript中没有强制执行隐私的功能,因此私有可访问性只在编译时强制执行,并作为对开发者使其成为私有的原始意图的警告。

作为一个例子,让我们把public 可访问性修改器添加到构造函数参数name ,并把private 可访问性修改器添加到成员color 。当我们给构造函数的一个参数添加publicprivate 可访问性时,该参数自动成为具有相关可访问性修改器的类的成员。

...
private color: string;
...
constructor ( public name: string, width: number, height: number ) {
...

errors in app

class.ts(24,41): The property 'color' does not exist on value of type 'Shape'

同样地,添加公共可访问性修饰符可以应用于构造函数的其他参数,即宽度和高度,如下图所示:

...
constructor (public name: string, public width: number, public height: number )
...

继承性

最后,你可以扩展一个现有的类,并通过extends 关键字从它创建一个派生类。让我们把下面的代码附加到现有的文件class.ts中,然后编译它:

class Shape3D extends Shape {

	volume: number;

	constructor ( public name: string, width: number, height: number, length: number ) {
		super( name, width, height );
		this.volume = length * this.area;
	};

	shoutout() {
		return "I'm " + this.name +  " with a volume of " + this.volume + " cm cube.";
	}

	superShout() {
		return super.shoutout();
	}
}

let cube = new Shape3D("cube", 30, 30, 30);
console.log( cube.shoutout() );
console.log( cube.superShout() );

派生的Shape3D 类中发生了一些事情:

  • 因为它是从Shape 类派生出来的,所以它继承了areacolor 属性。
  • 在构造方法里面,super 方法调用基类的构造方法,Shape ,传递namewidth ,和height 的值。继承允许我们重新使用来自Shape 的代码,所以我们可以很容易地用继承的area 属性计算this.volume
  • 方法shoutout() 重写了基类的实现,一个新的方法superShout() 通过使用super 关键字直接调用基类的shoutout() 方法。

只需增加几行代码,我们就可以轻松地扩展基类,以增加更多具体的功能,并通过TypeScript表达我们的意图:

extending the base class


我们才刚刚开始

试用TypeScript很容易。如果你喜欢大型应用程序的静态类型化方法,那么TypeScript的功能将执行一个熟悉的、有规律的环境。虽然它被比作CoffeeScriptDart,但TypeScript的不同之处在于它并不取代JavaScript;它为JavaScript增加了功能。

尽管微软已经承诺将TypeScript的许多功能(不包括类型注释)与ECMAScript 6保持一致,但该语言的未来发展仍是未知数。因此,如果你想尝试ES6的许多功能,TypeScript是一个很好的方法。来吧--试一试!