TypeScript相关知识
使用 TypeScript 的好处
JavaScript 已经很棒了,你或许会怀疑,我真的需要学习 TypeScript 吗?从技术层面上来说,成为一位出色的开发者确实不需要学习 TypeScript,大多数人没有学习 TypeScript 也做的很好。但是,工作中使用 TypeScript 确实有许多好处:
- 基于静态类型,用 TypeScript 编辑代码有更高的预测性,更易纠错。
- 由于模块,命名空间和强大的面向对象编程支持,使构建大型复杂应用程序的代码库更加容易。
- TypeScript在编译为JavaScript的过程中,在它到达运行时间前可以捕获所有类型的错误,并中断它们的执行。
- Angular 2 框架就是用 TypeScript 编写的,同时推荐开发人员在项目中也使用这种语言。
安装TypeScript
安装 TypeScript 最简单的方式就是通过 npm。使用以下命令行,可以全局安装 TypeScript 包,然后就可以在所有项目中使用TypeScript编译器了:
npm install -g typescript
打开终端然后运行 tsc -v 命令来查看是否正确安装了 TypeScript.
tsv -v
Version 3.8.1
第一个例子
新建一个 greeter.ts
文件
function greeter(person) {
return "Hello, " + person;
}
let user = "Jane User";
document.body.innerHTML = greeter(user);
编译成 JavaScript
TypeScript 是 写在 .ts 文件(或者 JSX的.tsx)里,不能直接在浏览器端运行,需要首先转译为vanilla.js。这个编译的过程可以有多种实现方式:
- 在终端上运行前面提到的命令行工具
tsc
。 - 直接在 Visual Studio 或者其他 IDE 和文本编辑器上(操作)。
- 使用自动化构建工具,
tsc greeter.ts
*这行命令是把 TypeScript 文件 index.ts编译为 JavaScript 版本的 greeter.js。如果 greeter.js 已经存在的话会被覆盖。
也可以通过列出所有的文件或者使用通配符来一次编译多个文件:
#Will result in separate .js files: main.js worker.js.
tsc main.ts worker.ts
#Compiles all .ts files in the current folder. Does NOT work recursively.
tsc *.ts
静态类型
TypeScript 一个很独特的特征是支持静态类型。意思就是可以声明变量的类型,(因此)编译器就可以确保赋值时不会产生类型错误。如果省略了类型声明,TypeScript 将会从代码中自动推测出正确的类型。
Number
//number
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
String
let color: string = "blue";
color = 'red';
也可以混合使用
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ fullName }.
I'll be ${ age + 1 } years old next month.`;
Array
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
Tuple
可以在一个数组中分别定义数据的类型
// Declare a tuple type
let x: [string, number];
// Initialize it
x = ["hello", 10]; // OK
// Initialize it incorrectly
x = [10, "hello"]; // Error
Enum
来自JavaScript的标准数据类型集的一种有用的补充则是枚举。就像C#这样的语言中,枚举是一种为数值集提供更友好名称的方法。
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;
枚举的一个便利的功能是,您还可以从数值转到枚举中该值的名称。例如,如果我们的值为2,但不确定上面的Color枚举中映射到了什么,我们可以查找相应的名称:
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];
console.log(colorName); // Displays 'Green' as its value is 2 above
Any
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)
let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.
如果你知道这种类型的某些部分,那么任何类型都很方便,但也有例外情况。例如,您可能有一个数组,但该数组有不同类型的混合:
let list: any[] = [1, true, "free"];
list[1] = 100;
Void
function warnUser(): void {
console.log("This is my warning message");
}
Null and Undefined
// Not much else we can assign to these variables!
let u: undefined = undefined;
let n: null = null;
Never
// Function returning never must have unreachable end point
function error(message: string): never {
throw new Error(message);
}
// Inferred return type is never
function fail() {
return error("Something failed");
}
// Function returning never must have unreachable end point
function infiniteLoop(): never {
while (true) {
}
}
以下是一些最常用的数据类型:
- Number (数值类型) – 所有数字都是数值类型的,无论是整数、浮点型或者其他数值类型都相同。
- String(字符串类型) – 文本类型,就如 vanilla JS 字符串一样可以使用单引号或者双引号。
- Boolean(布尔类型) – true 或者 false,用 0 和 1 会造成编译错误。
- Tuple (元组)– 元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
- Any(任意类型) – 该类型的变量可以设定为字符串类型,数值类型或者任何其他类型。
- Enum (枚举) - 对JavaScript标准数据类型的一个补充。 像C#等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字。
- Arrays(数组类型) – 有两种语法:my_arr: number[];或者my_arr: Array<number>
- Void (空类型)- 用在不返回任何值的函数中。
- Null 和 Undefined - TypeScript里,undefined和null两者各自有自己的类型分别叫做undefined和null。 和 void相似,它们的本身的类型用处不是很大。
- Never - 表示的是那些永不存在的值的类型。
接口通常会根据一个对象是否符合某种特定结构来进行类型检查。通过定义一个接口我们可以命名一个特殊的组合变量,确保它们会一直一起运行。当转译成 JavaScript 时,接口会消失 – 它们唯一的目的是在开发阶段里起到辅助的作用。
在下面的例子中我们定义了一个简单的接口来对一个函数自变量进行类型检查:
// Here we define our Food interface, its properties, and their types.
interface Food {
name: string;
calories: number;
}
// We tell our function to expect an object that fulfills the Food interface.
// This way we know that the properties we need will always be available.
function speak(food: Food): void{
console.log("Our " + food.name + " has " + food.calories + " calories.");
}
// We define an object that has all of the properties the Food interface expects.
// Notice that types will be inferred automatically.
var ice_cream = {
name: "ice cream",
calories: 200
}
speak(ice_cream);
类
在搭建大型规模的应用程序时,尤其是在 Java 或 C# 当中,许多开发者会优先选择面向对象编程。TypeScript 提供一个类系统,和 Java、C# 中的非常相似,包括了继承,抽象类,接口实现,setters/getters 方法等。
值得一提的是由于最新的 JavaScript 更新(ECMAScript 2015),这些类对于 vanilla JS 来说是原生的,并且在没有 TypeScript 的情况下也可以使用。这两种实现方式非常相似但是也有不同的地方,TypeScript 更加严格一些。
继续上面的 food的 例子,这里有一个简单的TypeScript类:
class Menu {
// Our properties:
// By default they are public, but can also be private or protected.
items: Array<string>; // The items in the menu, an array of strings.
pages: number; // How many pages will the menu be, a number.
// A straightforward constructor.
constructor(item_list: Array<string>, total_pages: number) {
// The this keyword is mandatory.
this.items = item_list;
this.pages = total_pages;
}
// Methods
list(): void {
console.log("Our menu for today:");
for(var i=0; i<this.items.length; i++) {
console.log(this.items[i]);
}
}
}
// Create a new instance of the Menu class.
var sundayMenu = new Menu(["pancakes","waffles","orange juice"], 1);
// Call the list method.
sundayMenu.list();
只要写过一点 Java 或者 C# ,就会发现TypeScript和它们在语法上非常相似。继承也是一样:
class HappyMeal extends Menu {
// Properties are inherited
// A new constructor has to be defined.
constructor(item_list: Array<string>, total_pages: number) {
// In this case we want the exact same constructor as the parent class (Menu),
// To automatically copy it we can call super() - a reference to the parent's constructor.
super(item_list, total_pages);
}
// Just like the properties, methods are inherited from the parent.
// However, we want to override the list() function so we redefine it.
list(): void{
console.log("Our special menu for children:");
for(var i=0; i<this.items.length; i++) {
console.log(this.items[i]);
}
}
}
// Create a new instance of the HappyMeal class.
var menu_for_children = new HappyMeal(["candy","drink","toy"], 1);
// This time the log message will begin with the special introduction.
menu_for_children.list();
泛型
泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型。
一段简单的脚本例子,传入一个参数,返回一个包含了同样参数的数组。
// The <T> after the function name symbolizes that it's a generic function.
// When we call the function, every instance of T will be replaced with the actual provided type.
// Receives one argument of type T,
// Returns an array of type T.
function genericFunc<T>(argument: T): T[] {
var arrayOfT: T[] = []; // Create empty array of type T.
arrayOfT.push(argument); // Push, now arrayOfT = [argument].
return arrayOfT;
}
var arrayFromString = genericFunc<string>("beep");
console.log(arrayFromString[0]); // "beep"
console.log(typeof arrayFromString[0]) // String
var arrayFromNumber = genericFunc(42);
console.log(arrayFromNumber[0]); // 42
console.log(typeof arrayFromNumber[0]) // number
第一次调用函数的时候,我们将类型手动设置成字符串。第二次及以后再次调用的时候就不必这样做了,因为编译器会判断传递过什么参数并且自动决定哪种类型最适合。虽然不是强制性的,但是由于编译器在众多复杂环境中确定正确类型的时候可能会失败,所以每次都传入类型是好的做法。
在开发大型应用时,另一个重要的概念是模块化。与一个有 10000 行代码的文件相比,把代码分成多个可复用组件,这样可以帮助项目保持条理性和易懂性。
TypeScript 介绍了导入和导出模块的语句,但是并不能解决文件间的真正连接。TypeScript 依赖于第三方函数库来加载外部模块:用于浏览器应用程序的 require.js 和用于 Node.js 的 CommonJS。我们来看一个简单的带有 require.js 的TypeScript 模块例子:
我们会有两个文件。一个是导出函数,另一个是导入并调用函数。
exporter.ts
var sayHi = function(): void {
console.log("Hello!");
}
export = sayHi;
importer.ts
import sayHi = require('./exporter');
sayHi();
现在我们需要下载 require.js,包含在一个script标签里 – 如何设置请点击这里。最后一步是编译这两个 .ts 文件。需要添加一个额外的参数来告诉 TypeScript,我们是为 require.js 创建模块的(也被称为AMD),而不是 CommonJS。
tsc --module amd *.ts
以上就是有关TypeScript的基本知识。