联合类型 (Union types)
概述
联合类型是一种强大的表达方式,用于表示一个值可以是多种类型中的一种。例如,你可能有一个用于运行程序的 API,它接受的命令行参数可以是 string、string[] 或者是一个返回 string 的函数。现在你可以这样编写:
tsinterface RunOptions {program: string;commandline: string[] | string | (() => string);}
对联合类型的赋值非常直观——任何可以赋值给联合类型成员的值,都可以赋值给该联合类型。
tsvar opts: RunOptions = /* ... */;opts.commandline = '-hello world'; // OKopts.commandline = ['-hello', 'world']; // OKopts.commandline = [42]; // Error, number is not string or string[]
当读取联合类型时,你可以访问它们所共有的属性。
tsif (opts.commandline.length === 0) {// OK, string and string[] both have 'length' propertyconsole.log("it's empty");}
使用类型保护 (Type Guards),你可以轻松地操作联合类型的变量。
tsfunction formatCommandline(c: string | string[]) {if (typeof c === "string") {return c.trim();} else {return c.join(" ");}}
更严格的泛型
由于联合类型能够表示广泛的类型场景,我们决定提高某些泛型调用的严格性。在此之前,像这样的代码(令人惊讶地)可以在没有错误的情况下编译通过。
tsfunction equal<T>(lhs: T, rhs: T): boolean {return lhs === rhs;}// Previously: No error// New behavior: Error, no best common type between 'string' and 'number'var e = equal(42, "hello");
有了联合类型,你现在可以在函数声明处和调用处指定期望的行为。
ts// 'choose' function where types must matchfunction choose1<T>(a: T, b: T): T {return Math.random() > 0.5 ? a : b;}var a = choose1("hello", 42); // Errorvar b = choose1<string | number>("hello", 42); // OK// 'choose' function where types need not matchfunction choose2<T, U>(a: T, b: U): T | U {return Math.random() > 0.5 ? a : b;}var c = choose2("bar", "foo"); // OK, c: stringvar d = choose2("hello", 42); // OK, d: string|number
更好的类型推断
联合类型还允许在数组以及其他可能包含多种类型值的集合中进行更好的类型推断。
tsvar x = [1, "hello"]; // x: Array<string|number>x[0] = "world"; // OKx[0] = false; // Error, boolean is not string or number
let 声明
在 JavaScript 中,var 声明会被“提升”到其所在作用域的顶部。这可能会导致令人困惑的 bug。
tsconsole.log(x); // meant to write 'y' here/* later in the same block */var x = "hello";
TypeScript 现在支持新的 ES6 关键字 let,它声明的变量具有更直观的“块级”作用域语义。let 变量只能在其声明之后被引用,并且其作用域限制在定义它的语法块中。
tsif (foo) {console.log(x); // Error, cannot refer to x before its declarationlet x = "hello";} else {console.log(x); // Error, x is not declared in this block}
let 仅在目标为 ECMAScript 6 (--target ES6) 时可用。
const 声明
TypeScript 支持的另一种新的 ES6 声明类型是 const。const 变量不能被重新赋值,并且必须在声明时进行初始化。这对于那些你不希望在初始化后更改其值的声明非常有用。
tsconst halfPi = Math.PI / 2;halfPi = 2; // Error, can't assign to a `const`
const 仅在目标为 ECMAScript 6 (--target ES6) 时可用。
模板字符串
TypeScript 现在支持 ES6 模板字符串。这是一种在字符串中嵌入任意表达式的简单方法。
tsvar name = "TypeScript";var greeting = `Hello, ${name}! Your name has ${name.length} characters`;
当编译为非 ES6 目标时,字符串会被分解处理。
jsvar name = "TypeScript!";var greeting ="Hello, " + name + "! Your name has " + name.length + " characters";
类型保护 (Type Guards)
JavaScript 中的一个常见模式是使用 typeof 或 instanceof 在运行时检查表达式的类型。TypeScript 现在能够理解这些条件,并会在 if 块中使用它们时相应地改变类型推断。
使用 typeof 测试变量
tsvar x: any = /* ... */;if(typeof x === 'string') {console.log(x.subtr(1)); // Error, 'subtr' does not exist on 'string'}// x is still any herex.unknown(); // OK
在联合类型和 else 中使用 typeof
tsvar x: string | HTMLElement = /* ... */;if(typeof x === 'string') {// x is string here, as shown above}else {// x is HTMLElement hereconsole.log(x.innerHTML);}
在类和联合类型中使用 instanceof
tsclass Dog { woof() { } }class Cat { meow() { } }var pet: Dog|Cat = /* ... */;if (pet instanceof Dog) {pet.woof(); // OK}else {pet.woof(); // Error}
类型别名
你现在可以使用 type 关键字为类型定义一个别名。
tstype PrimitiveArray = Array<string | number | boolean>;type MyNumber = number;type NgScope = ng.IScope;type Callback = () => void;
类型别名与其原始类型完全相同;它们仅仅是替代名称。
const enum(完全内联枚举)
枚举非常有用,但有些程序实际上并不需要生成的代码,只需将枚举成员的所有实例内联为其数值等价物即可。新的 const enum 声明在类型安全方面与普通 enum 的工作方式相同,但在编译时会完全抹除。
tsconst enum Suit {Clubs,Diamonds,Hearts,Spades}var d = Suit.Diamonds;
编译为
jsvar d = 1;
TypeScript 现在也将在可能的情况下计算枚举值。
tsenum MyFlags {None = 0,Neat = 1,Cool = 2,Awesome = 4,Best = Neat | Cool | Awesome}var b = MyFlags.Best; // emits var b = 7;
-noEmitOnError 命令行选项
TypeScript 编译器的默认行为是:即使存在类型错误(例如,尝试将 string 赋值给 number),仍会生成 .js 文件。在构建服务器或其他仅希望从“干净”构建中获取输出的场景中,这可能是不受欢迎的。新的标记 noEmitOnError 可以防止编译器在存在任何错误时生成 .js 代码。
这现在是 MSBuild 项目的默认设置;这使得 MSBuild 的增量构建能够按预期工作,因为输出仅在干净构建时生成。
AMD 模块名称
默认情况下,生成的 AMD 模块是匿名的。当使用其他工具(如打包器 r.js)处理生成的模块时,这可能会导致问题。
新的 amd-module name 标签允许向编译器传递可选的模块名称。
ts//// [amdModule.ts]///<amd-module name='NamedModule'/>export class C {}
这将导致在调用 AMD define 时,为模块分配名称 NamedModule。
js//// [amdModule.js]define("NamedModule", ["require", "exports"], function(require, exports) {var C = (function() {function C() {}return C;})();exports.C = C;});