原文是 What's new in TypeScript 的一部分html
TypeScript 有两个特殊的类型,null 和 undefined,null
和 undefined
分别是它们的值。之前是不能显式使用这些类型的,但如今 null
和 undefined
无论在什么类型检查模式下均可以用做类型名称。node
之前的类型检查器认为 null
和 undefined
能够赋值给任意变量。实际上,null
和 undefined
对每个类型都是有效的值,任何类型都不可能明肯定义不包含它们(所以不可能检查到对它们的错误使用)。jquery
--strictNullChecks
--strictNullChecks
参数用于新的严格空检查模式。git
在严格空检查模式下, null
和 undefined
值都 不 属于任何一个类型,它们只能赋值给本身这种类型或者 any
(有一个例外,undefined
也能够赋值给 void
)。所以,在常规类型检查模式下 T
和 T | ndefined
被认为是等同的(由于 undefined
被看做 T
的子类型),但它们在严格类型检查模式下是不一样的类型,只有 T | undefined
类型容许出现 undefined
值。T
和 T | null
也是这种状况。es6
// 使用 --strictNullChecks 参数编译 let x: number; let y: number | undefined; let z: number | null | undefined; x = 1; // 正确 y = 1; // 正确 z = 1; // 正确 x = undefined; // 错误 y = undefined; // 正确 z = undefined; // 正确 x = null; // 错误 y = null; // 错误 z = null; // 正确 x = y; // 错误 x = z; // 错误 y = x; // 正确 y = z; // 错误 z = x; // 正确 z = y; // 正确
在严格空检查模式下,编译器要求在任意可能先进到的代码路径前,不容许 undefined
值的变量引用都必须已经赋值。github
// 使用 --strictNullChecks 参数编译 let x: number; let y: number | null; let z: number | undefined; x; // 错误, 使用前未赋值 y; // 错误, 使用前未赋值 z; // 正确 x = 1; y = null; x; // 正确 y; // 正确
编译器经过 基于控制流的类型分析 来检查变量是否确实赋值. 稍后请进一步阅读关于这个主题的细节。web
可选参数和属性会自动将 undefined
加入它们的类型,哪怕在它们的类型申明中没有特别指定 undefined
。好比下面两个类型就是一致的。typescript
// 使用 --strictNullChecks 参数编译 type T1 = (x?: number) => string; // x 类型是 number | undefined type T2 = (x?: number | undefined) => string; // x 类型是 number | undefined
若是一个对象是 null
或 undefined
,访问它的属性会引起编译错误,对 null
或 undefined
进行函数调用也会引起编译错误。不过类型检查进行了扩展,支持对非空和非未定义类型进行检查。shell
// 使用 --strictNullChecks 参数编译 declare function f(x: number): string; let x: number | null | undefined; if (x) { f(x); // 正确, 这里 x 的类型是 number } else { f(x); // 错误, 这里 x 是 number? } let a = x != null ? f(x) : ""; // a 的类型是字符串 let b = x && f(x); // b 的类型是 string | 0 | null | undefined
非空和非未定义类型检查容许使用 ==
、!=
、===
、或者 !==
运算符来与 null
或者 undefined
进行比较,好比 x != null
或 x === undefined
。具体效果与 JavaScript 的语义一致。(例如,双等号运算符检查两个值,无论哪个是指定的,而三等号运算符只检查指定的值)。json
之前的类型控制只能检查局部变量和参数。而如今它能够检查“带点的名称〔译者注:带点指对象后面的点号运算符〕”,这类名称由变量或参数以及后面的一个或多个属性访问组成。
interface Options { location?: { x?: number; y?: number; }; } function foo(options?: Options) { if (options && options.location && options.location.x) { const x = options.location.x; // x 的类型是 number } }
对带点号和名称的类型控制也会应用于用户定义的类型控制功能,以及 typeof
和 instanceof
运算符,并且算不须要 --strictNullChecks
编译参数。
对带点号的名称的类型控制会在对其中任意部分赋值后失败。好比,x.y.z
的类型控制在 x
、x.y
或 x.y.z
赋值后将失去效用。
表达式运算符容许操做数类型包括 null
或/和 undefined
,但结果必定是非空非未定义的类型。
// 使用 --strictNullChecks 参数编译 function sum(a: number | null, b: number | null) { return a + b; // 结果类型是 number }
&&
运算符的会根据在操做数的类型来添加 null
或/和 undefined
类型到右操做数的类型中。||
则会从左操做数的类型中去掉 null
和 undefined
后,再用于推导结果类型。
// 使用 --strictNullChecks 参数编译 interface Entity { name: string; } let x: Entity | null; let s = x && x.name; // s 类型是 string | null let y = x || { name: "test" }; // y 类型是 Entity
在严格空检查模型下,null
和 undefined
不会 扩展为 any
。
let z = null; // z 是 null
由于类型扩展,在常规类型检查模式下 z
被推导为 any
,但在严格空类型检查模式下对 z
的类型推导结果仍然是 null
(而且,因为没有指定类型,null
是 z
惟一可能的值)。
新的 !
后置运算符用于断言它的操做数在检查器不能推断的状况下是非空非未定义的。举例说明:x!
申明 x
的值不多是 null
或 undefined
。与 <T>x
和 x as T
这两种形式的类型申明类似,在生成 JavaScript 代码时只是简单地去掉了 !
非空断言运算符。
// 使用 --strictNullChecks 参数编译 function validateEntity(e?: Entity) { // 若是 e 是 null 或者无效的 Entity,抛出异常 } function processEntity(e?: Entity) { validateEntity(e); let s = e!.name; // 断言 e 非空,而后访问它的 name 属性 }
这个新特性设置为能够在严格空检查模式和常规类型检查模式下均可使用。具体来讲,在常规类型检查模式下,null
和 undefined
类型会自动从联合类型中剔除(由于它们已是其它类型的子类型了),!
非空断言运算符容许存在但在常规类型检查模式下不会有任何做用。这样一来,使用了非空非未定义类型的申明文件就能够向后兼容,在常规类型检查模型下使用。
在实际应用中,严格空检查模式要求全部用于编译的文件都是可识别空和未定义的。
TypeScript 2.0 实现了基于控制流的类型分析,用于控制局部变量和参数。以前,用于类型控制的类型分析局限于 if
语句和 ?:
条件表达式,并不能用于赋值和控制流结构,如 return
和 break
语句。无论这个拥有联合类型的变量和参数出如今什么地方,TypeScript 2.0 让类型检查分析贯穿于全部可能的流程,包括可能会产生极特别类型(缩小范围的类型)的语句和表达式。
function foo(x: string | number | boolean) { if (typeof x === "string") { x; // x 是 string 类型 x = 1; x; // x 是 number 类型 } x; // x 是 number | boolean 类型 } function bar(x: string | number) { if (typeof x === "number") { return; } x; // x 是 string 类型 }
基于控制流的类型分析与 --strictNullChecks
模式极为相关,由于可空类型使用联合类型来表示:
function test(x: string | null) { if (x === null) { return; } x; // 在函数后面的部分,x 是 string 类型 }
此外,--strictNullChecks
模式中,对不容许为 undefined
变量,基于控制流的类型分析还包含了 精确的赋值分析。
function mumble(check: boolean) { let x: number; // 这个类型不容许 undefined 值 x; // 错误, x 是 undefined if (check) { x = 1; x; // 正确 } x; // 错误, x 有多是 undefined x = 2; x; // 正确 }
TypeScript 2.0 开始支持推断(或可识别)联合类型。特别指出,TS 编译器如今支持限制联合类型来对类型进行保护。这基于代码中对标识属性的检查。这项功能也被扩展到 switch
语句。
interface Square { kind: "square"; size: number; } interface Rectangle { kind: "rectangle"; width: number; height: number; } interface Circle { kind: "circle"; radius: number; } type Shape = Square | Rectangle | Circle; function area(s: Shape) { // 下面的 switch 语句中,每一个 case 子句都限制了 s 的类型。 // 根据对标识属性值的判断,这使得既然不申明类型也能够根据推断出来的类型访问其它属性。 switch (s.kind) { case "square": return s.size * s.size; case "rectangle": return s.width * s.height; case "circle": return Math.PI * s.radius * s.radius; } } function test1(s: Shape) { if (s.kind === "square") { s; // Square } else { s; // Rectangle | Circle } } function test2(s: Shape) { if (s.kind === "square" || s.kind === "rectangle") { return; } s; // Circle }
标识属性类型控制 来自于诸如 x.p == v
、x.p === v
、x.p != v
或 x.p !== v
这样的表达式,p
和 v
是 string 字面类型〔译者注:常量〕或者是一个 string 字面常量的联合〔译者注:好比 "type1" | "type2" | "type3" 这样〕。x
有一个 p
属性,该属性有一个可能的值 v
,标识属性类型控制据此能够推断 x
更精确的类型。
注意,目前咱们仅支持标识属性是 string 字面量类型的状况。咱们计划后面添加对布尔和数值字面量的支持。
never
类型TypeScript 2.0 引入了新的基本类型 never
。never
类型值表现为从未发生。 具体说来,never
用于函数的返回值申明,而这个函数实际没有返回任何东西。在类型控制做用下,never
是不可能做为变量类型的。
never
类型有以下一些特征:
never
是全部类型的子类型,便可以赋值给任意类型。never
的子类型,因此不能赋值给 never
(never
本身除外)。return
语句,或者 return
语句返回的就是 结果为 never
的表达式,又或者函数结尾不可达(由控制流程分析判断),则推断函数的返回类型是 never
。never
,全部 return
语句(若是有的话)必须返回结果为 never
的表达式,而且必定不可能到达函数结尾。由于 never
是任何类型的子类型,因此通常不会在联合类型中指定,而且若是函数中推导出来有其它类型返回,never
就会被忽略。
一些返回 never
的函数示例:
// 函数不能到达结束点,返回类型是 never function error(message: string): never { throw new Error(message); } // 推导返回类型是 never function fail() { return error("Something failed"); } // 函数不能到达结束点,返回类型是 never function infiniteLoop(): never { while (true) { } }
一些使用返回 never
的函数的函数示例:
// 推导返回类型是 number function move1(direction: "up" | "down") { switch (direction) { case "up": return 1; case "down": return -1; } return error("Should never get here"); } // 推导返回类型是 number function move2(direction: "up" | "down") { return direction === "up" ? 1 : direction === "down" ? -1 : error("Should never get here"); } // 推导返回类型是 T function check<T>(x: T | undefined) { return x || error("Undefined value"); }
因为 never
能够赋值给任意类型,返回 never
的函数能够用于返回特定类型的回调函数:
function test(cb: () => string) { let s = cb(); return s; } test(() => "hello"); test(() => fail()); test(() => { throw new Error(); })
如今经过 readonly
修饰符,属性或索引能够被申明为只读的。
只读属性能够拥有初始化器,也能够定义它的类的构造函数中赋值,其它状况下都是不容许赋值的。
另外,有一些状况会产生 隐式的 只读申明。
get
访问器没有 set
访问器的属性被认为是只读的。const
变量是只读的。import
语句中申明的实体是只读的。import * as foo from "foo"
中申明了 foo
,这时 foo.x
是只读的。interface Point { readonly x: number; readonly y: number; } var p1: Point = { x: 10, y: 20 }; p1.x = 5; // 错误, p1.x 只读 var p2 = { x: 1, y: 1 }; var p3: Point = p2; // 正确, p2 的只读别名〔由于 Point 中的属性定义为 readonly〕 p3.x = 5; // 错误, p3.x 只读 p2.x = 5; // 正确, 同时也改变了 p3.x,由于 p3 是 p2 的(只读)别名
class Foo { readonly a = 1; readonly b: string; constructor() { this.b = "hello"; // 构造函数中容许赋值 } }
let a: Array<number> = [0, 1, 2, 3, 4]; let b: ReadonlyArray<number> = a; b[5] = 5; // 错误, 元素只读 b.push(5); // 错误, 没有 push 方法 (由于它是 array 的变种) b.length = 3; // 错误, length 只读 a = b; // 错误, 因为变种,部分方法已经不存在了
this
继为类和接口指定 this
类型以后,函数和方法也能够申明它们所指望的 this
类型了。
默认状况下函数内部的 this
类型是 any
。从 TypeScript 2.0 开始,能够显示的指代一个 this
参数。this
参数不是一个真实的参数,并且它必须放在参数列表的第一位:
function f(this: void) { // 确保在这个独立的函数中不会用到 `this` }
this
参数在功能库中,this
参数可用于申明回调函数如何调用。
interface UIElement { addClickListener(onclick: (this: void, e: Event) => void): void; }
this: void
表示 addClickListener
但愿 onclick
是一个不须要 this
类型的函数。
如今若是须要使用 this
调用:
class Handler { info: string; onClickBad(this: Handler, e: Event) { // 天啊,这里用了 this,使用这个回调在运行时会致使巨大的错误 this.info = e.message; }; } let h = new Handler(); uiElement.addClickListener(h.onClickBad); // 错误!
--noImplicitThis
TypeScript 2.0 中加入了一个参数,标记全部函数中的 this
都没有申明类型。
tsconfig.json
支持 Glob支持 Glob 啦!!支持 Glob 是最受欢迎特性中的一个.
"include"
和 "exclude"
两个参数支持使用 Glob 形式的文件模板。
{ "compilerOptions": { "module": "commonjs", "noImplicitAny": true, "removeComments": true, "preserveConstEnums": true, "outFile": "../../built/local/tsc.js", "sourceMap": true }, "include": [ "src/**/*" ], "exclude": [ "node_modules", "**/*.spec.ts" ] }
支持的 Glob 通配符包括:
*
匹配 0 个或更多字符(不包含目录分隔符)?
匹配 1 个字符(不包含目录分隔符)**/
递归匹配任意子目录若是一段 Glob 模板只包含 *
或 .*
,则只有支持的文件扩展名被包含在内(如:默认的 .ts
、.tsx
和 .d.ts
,若是 allowJs
设置为 true,则还有 .js
和 .jsx
)。
若是 "files"
和 "include"
都未指定,编译器默认包含全部包含目录及子目录下的 TypeScript(.ts
、.d.ts
和 .tsx
) 文件,不过要排除 "exclude"
中指定的那些。若是 allowJs
设置为 true,JS 文件 (.js
and .jsx
) 也会包含在内。
若是指定了 "files"
或 "include"
属性,编译器会合并两个属性指定的文件。"outDir"
选项指定目录中的文件老是被排除在外,除非在 "files"
中特别指定("exclude"
属性中指定的也是这样)。
"include"
包含的文件能够被 "exclude"
属性过滤。然而 "files"
属性指定的文件则无论 "exclude"
属性的设置。"exclude"
属性未设置时,默认会排除 node_modules
、bower_components
和 jspm_packages
目录。
TypeScript 2.0 提供了一系列的模块解决方案工具来通知编译器在哪里找到给定模块的申明。
参阅 模块解决方案文档进行具体了解。
baseUrl
是 AMD 模块加载系统经常使用的办法,它描述了模块在运行时应该从哪个目录“展开”。全部未指定相对路径的导入都假定相对于 baseUrl
。
{ "compilerOptions": { "baseUrl": "./modules" } }
导入 "moduleA"
时会在 ./modules/moduleA
中查找。
import A from "moduleA";
有时候模块并不直接放在 baseUrl 下。加载器使用一个映射配置在模块名称和文件之间创建映射关系。参阅
RequireJs 文档 和 SystemJS 文档.
TypeScript 编译器支持在 tsconfig.json
文件中使用 "pathes"
属性申明相似的映射。
导入模块 "jquery"
会在运行时转换为 "node_modules/jquery/dist/jquery.slim.min.js"
.
{ "compilerOptions": { "baseUrl": "./node_modules", "paths": { "jquery": ["jquery/dist/jquery.slim.min"] } }
"paths"
也能够进行复杂的映射,好比回退了多级的位置。想像一下,某个项目配置了一些模块在某个位置,而其它的模块在另外一个位置。
rootDirs
带来的虚拟目录能够用 'rootDirs' 通知编译器把 根 都看成一个“虚拟”目录;而后编译器能够把全部“虚拟”目录假设为一个目录,并在此经过相对路径找到导入的模块。
假设有这样一个项目结构
src └── views └── view1.ts (imports './template1') └── view2.ts generated └── templates └── views └── template1.ts (imports './view2')
某个构建步骤会从 /src/views
和 /generated/templates/views
拷贝到输出目录中的同一个目录里。在运行的时候,视图但愿模板就在它的同级目录下,这样就可使用相对名称 "./template"
来导入了。
"rootDirs"
指定了一个 根 列表,包含了指望在运行时放在一块儿的内容。在这个示例中,tsconfig.json
文件看起来就像这样:
{ "compilerOptions": { "rootDirs": [ "src/views", "generated/templates/views" ] } }
--traceResolution
提供了一个方便的方式来让编译器知道该如何找到模块。
tsc --traceResolution
若是你不想在使用一个新模块的时候花时间去写它的申明,你如今能够简单地使用速配环境来达到目的。
declare module "hot-new-module";
从速配模块导入的变量都是 any 类型。
import x, {y} from "hot-new-module"; x(y);
以前想经过模块加载器扩展(例如 AMD 或者 SystemJS) 十分不易;之前须要为每一个资源定义环境模块申明。
TypeScript 2.0 支持使用通配符(*
)申明一“组”模块名称;这种方法使得只须要为扩展申明一次,而没必要为每一个资源进行申明。
declare module "*!text" { const content: string; export default content; } // 有些会用另外一种形式 declare module "json!*" { const value: any; export default value; }
这样就能够导入与 "*!text"
或 "json!*"
匹配的资源。
import fileContent from "./xyz.txt!text"; import data from "json!http://example.com/data.json"; console.log(data, fileContent);
在从无类型代码中迁移代码时,通配符模块也很是有用。若是与模块申明的速配环境结合,很是容易地就能将一系列的模块看成 any
申明。
declare module "myLibrary/*";
从 myLibrary
下的模块导入的内容都被编译器看成 any
类型;这直接关闭了这些模块的形式或类型检查。
import { readFile } from "myLibrary/fileSystem/readFile`; readFile(); // readFile 是 'any'
有些库被设置为容许多处模块加载器加载,或者不须要使用加载器(全局变量)。知名的有 UMD 和 Isomorphic 模块。这些库既能够经过 import 导入使用,也能够经过设置全局变量来使用。
例如:
export const isPrime(x: number): boolean; export as namespace mathLib;
以后这个库在模块中经过导入使用:
import { isPrime } from "math-lib"; isPrime(2); mathLib.isPrime(2); // 错误: 不能在模块内使用全局定义
它也能够看成全局变量使用,但只能在脚本中这样作。(脚本指不包含导入导出的文件。)
mathLib.isPrime(2);
如今类中能够定义可选的类属性和方法,这在接口中早就实现并为你们所熟知了。
class Bar { a: number; b?: number; f() { return 1; } g?(): number; // 可选方法的方法体能够省略掉 h?() { return 2; } }
在 --strictNullChecks
模式下编译时,可选属性和方法的类型中会自动包含 undefined
。所以上面示例中的 b
属性是 number | undefined
类型,而 g
方法是 (() => number) | undefined
类型。
类型控制会在适当的时机将 undefined
从类型中剥离出去:
function test(x: Bar) { x.a; // number x.b; // number | undefined x.f; // () => number x.g; // (() => number) | undefined let f1 = x.f(); // number let g1 = x.g && x.g(); // number | undefined let g2 = x.g ? x.g() : 0; // number }
类构造函数能够申明为 private
或 protected
。具备私有构造函数的类不能在外部实例化,也不能被继承。具备保护构造函数的类不能在外部实例化,但能够被继承。
class Singleton { private static instance: Singleton; private constructor() { } static getInstance() { if (!Singleton.instance) { Singleton.instance = new Singleton(); } return Singleton.instance; } } let e = new Singleton(); // 错误: 'Singleton' 的构造函数是私有的 let v = Singleton.getInstance();
抽象类能够申明抽象属性和抽象访问器。子类中须要定义抽象属性,或者继续标记为抽象的。抽象属性不能初始化。抽象访问器不能有函数体。
abstract class Base { abstract name: string; abstract get value(); abstract set value(v: number); } class Derived extends Base { name = "derived"; value = 1; }
若是一个对象字面量的全部属性都符合某个索引特性,那么这个对象字面量类型就就能够赋值给它。这样对于须要一个映射或字典做为参数的函数,就能够接受初始化为相应对象字面量的变量了:
function httpService(path: string, headers: { [x: string]: string }) { } const headers = { "Content-Type": "application/x-www-form-urlencoded" }; httpService("", { "Content-Type": "application/x-www-form-urlencoded" }); // 正确 httpService("", headers); // 如今正确,而之前是错误的
--lib
包含内置类型申明输入 --lib
可让 ES6/ES2015 内置 API 申明仅限于 target: ES6
。经过 --lib
选项你能够选择一些内置 API 申明组包含在项目中。假如你但愿运行时支持 Map
、Set
和 Promise
(大部分新浏览器都支持),只须要使用参数 --lib es2015.collection,es2015.promise
。
与之相似,也能够从项目中排除一些不须要的申明,好比你在 node 项目中就不须要包含 DOM,那么可使用 --lib es5,es6
。
这里有一个支持的 API 组列表:
tsc --target es5 --lib es5,es2015.promise
"compilerOptions": { "lib": ["es5", "es2015.promise"] }
--noUnusedParameters
和 --noUnusedLocals
标记未使用的申明TypeScript 2.0 有两个参数可帮助你保持代码简洁。--noUnusedParameters
参数会将未使用的函数和方法参数标记为错误。--noUnusedLocals
会将未使用的局部(未导出)申明,包含变量、函数、类、导入等,标记出来。在使用 --noUnusedLocals
参数的状况下,未使用的私有类成员也会被标记为错误。
import B, { readFile } from "./b"; // ^ 错误: `B` 申明但未使用 readFile(); export function write(message: string, args: string[]) { // ^^^^ 错误: 'arg' 申明但未使用 console.log(message); }
以 _
开始的参数申明会被“未使用”参数检查忽略。例如:
function returnNull(_a) { // 正确 return null; }
.js
扩展名TypeScript 2.0 之前,模块识别会忽略扩展名。好比,导入 import d from "./moduleA.js"
,编译器会在 ./moduleA.js.ts
或者 ./moduleA.js.d.ts
中查找 "moduleA.js"
中的定义。这使得经过 URI 标识来使用一些像SystemJS 那样的构建或加载工具比较困难。
TypeScript 2.0 的编译器会在 ./moduleA.ts
或 ./moduleA.d.ts
中去查找 "moduleA.js"
中的定义。
之前的版本中 target: es5
和 module: es6
参数不一样合并使用,但如今能够了。这能促进使用基于 ES2015 的 Tree-Shaking 工具,好比 rollup。
如今容许函数参数列表或调用参数列表后面出现逗号了。这在 Stage-3 ECMAScript 建议 中提出,对 ES3/ES5/ES6 均有效。
function foo( bar: Bar, baz: Baz, // 容许参数列表后面的逗号 ) { // 实现... } foo( bar, baz, // 容许调用参数列表后面的逗号 );
--skipLibCheck
TypeScript 2.0 添加了一个新编译参数 --skipLibCheck
,这个参数会让编译器跳过对申明文件(扩展名是 .d.ts
的文件)的类型检查。若是一个程序包含大量的申明文件,编译器会花不少时间去检查这些已知没有错误的申明。若是跳过对这些申明文件的检查,编译时间会获得显著提高。
因为一个文件中的申明可能影响其它文件的类型检查,因此使用 --skipLibCheck
参数后可能会致使某些错误被不被探测到。好比,一个非申明文件使用了某个申明文件中申明的类型,那只有在申明文件被检查的时候才可能发现并报告错误。固然这种状况极不容易发生。
在多个申明文件中为某个接口定义了相同的成员,这一般会致使重复定义错误。
TypeScript 2.0 放宽了这个限制。若是两个定义块中出现重复定义,只要它们是 彻底相同的 类型就不会有问题。
在同一个定义块中定义重复的类型仍然是错误的。
interface Error { stack?: string; } interface Error { code?: string; path?: string; stack?: string; // 正确 }
--declarationDir
--declarationDir
容许在与生成的 JavaScript 文件不一样的位置生成申明文件。