TypeScript 2.0

TypeScript 2.0

空和未定义的类型

TypeScript 有两个特殊的类型,Null 和 Undefined,它们分别具有值null和值undefined。以前这是不可能明确地命名这些类型,但nullundefined现在可以不管的类型检查模式作为类型名称。

类型检查以前被认为nullundefined分配到任何东西。有效地,nullundefined每种类型的有效值,并且不可能特别排除它们(因此不可能检测到它们的错误使用)。

--strictNullChecks

--strictNullChecks switches to a new strict null checking mode.

在严格的空检查模式下,nullundefined不在每个类型的域中,并且只能赋予它们自己和anyundefined可以赋值的一个例外void)。所以,而TT | undefined被认为是在常规类型检查模式是同义的(因为undefined被认为是任何一种亚型T),它们是不同类型的严格的类型检查模式,并且仅T | undefined允许undefined值。TT | null的关系也是如此。

示例

// Compiled with --strictNullChecks let x: number; let y: number | undefined; let z: number | null | undefined; x = 1; // Ok y = 1; // Ok z = 1; // Ok x = undefined; // Error y = undefined; // Ok z = undefined; // Ok x = null; // Error y = null; // Error z = null; // Ok x = y; // Error x = z; // Error y = x; // Ok y = z; // Error z = x; // Ok z = y; // Ok

分配之前使用检查

在严格的空检查模式下,编译器要求每个引用一个类型的局部变量,undefined该变量在每个可能的前面的代码路径中都不包含前面的变量。

示例

// Compiled with --strictNullChecks let x: number; let y: number | null; let z: number | undefined; x; // Error, reference not preceded by assignment y; // Error, reference not preceded by assignment z; // Ok x = 1; y = null; x; // Ok y; // Ok

编译器通过执行基于控制流的类型分析来检查变量是否明确分配。有关此主题的更多详细信息,请参阅稍后

可选参数和属性

可选参数和属性自动undefined添加到它们的类型中,即使它们的类型注释没有特别包含undefined。例如,以下两种类型是相同的:

// Compiled with --strictNullChecks type T1 = (x?: number) => string; // x has type number | undefined type T2 = (x?: number | undefined) => string; // x has type number | undefined

非空和非未定义的类型的保护

如果对象或函数的类型包含nullundefined,则属性访问或函数调用会产生编译时错误。但是,类型警卫被扩展为支持非空和非未定义的检查。

示例

// Compiled with --strictNullChecks declare function f(x: number): string; let x: number | null | undefined; if (x) { f(x // Ok, type of x is number here } else { f(x // Error, type of x is number? here } let a = x != null ? f(x) : ""; // Type of a is string let b = x && f(x // Type of b is string | 0 | null | undefined

非空和非不确定型后卫可以使用==!====,或!==运营商来比较null或者undefined,如x != nullx === undefined。对主体变量类型的影响准确地反映了 JavaScript 语义(例如,无论指定哪一个值,double-equals(双等) 运算符都检查这两个值,而 triple-equals(三联等) 仅检查指定值)。

类型保护中的虚线名称

以前只能使用类型防护检查局部变量和参数。类型守卫现在支持检查包含变量或参数名称的“虚线名称”,后跟一个或多个属性访问。

示例

interface Options { location?: { x?: number; y?: number; }; } function foo(options?: Options) { if (options && options.location && options.location.x) { const x = options.location.x; // Type of x is number } }

用于虚线名称的类型守护程序也可以与用户定义的类型保护函数以及typeofinstanceof运算符一起使用,并且不依赖于--strictNullChecks编译器选项。

在虚线名称的任何部分分配后,虚线名称的类型保卫不起作用。例如,对于一个型保护x.y.z将具有分配给以下没有影响xx.yx.y.z

表达式运算符

表达式运算符允许操作数类型包含null和/或undefined始终生成非空和非未定义类型的值。

// Compiled with --strictNullChecks function sum(a: number | null, b: number | null) { return a + b; // Produces value of type number }

&&运算符增加了null和/或undefined右操作数,这取决于存在于左操作数的类型的类型,和||操作者同时删除nullundefined从在所得到的联合类型的左操作数的类型。

// Compiled with --strictNullChecks interface Entity { name: string; } let x: Entity | null; let s = x && x.name; // s is of type string | null let y = x || { name: "test" }; // y is of type Entity

类型加宽

在严格的空检查模式下,nullundefined类型不会扩大any

let z = null; // Type of z is null

在常规类型检查模式中,推断的类型zany因为扩展,但是在严格的空检查模式下,推断的类型znull(因此,缺少类型注释,null是唯一可能的值z)。

非空断言运算符

在类型检查器无法推断该事实的上下文中,可以使用新的!在类型检查器无法推断该事实的上下文中,可以使用新的后修复表达式运算符来断言其操作数为非空且非未定义的。具体来说,该操作x!会生成x带有null和 undefined 的值。类似键入的形式断言<T>x和x as T,所述!非空断言操作者简单地在所发射的JavaScript代码移除。

// Compiled with --strictNullChecks function validateEntity(e?: Entity) { // Throw exception if e is null or invalid entity } function processEntity(e?: Entity) { validateEntity(e let s = e!.name; // Assert that e is non-null and access name }

兼容性

这些新功能的设计可以用于严格空检查模式和常规检查模式。特别是,在常规类型检查模式下nullundefined类型会自动从联合类型中删除(因为它们是所有其他类型的子类型),并且!允许使用非空断言表达式运算符,但在常规类型检查模式中不起作用。因此,为了向后兼容,更新为使用 nullundefined-aware 类型的声明文件仍可以用于常规类型检查模式。

实际上,严格的空检查模式要求编译中的所有文件都是空的和未定义的。

基于控制流的类型分析

TypeScript 2.0 实现了局部变量和参数的基于控制流的类型分析。以前,对类型警卫进行的类型分析仅限于if语句和?:条件表达式,并且不包括分配和控制流结构(例如returnbreak语句)的影响。使用 TypeScript 2.0,类型检查器会分析语句和表达式中所有可能的控制流,以在任何给定位置为声明具有联合类型的局部变量或参数生成最可能的特定类型(缩小类型)。

示例

function foo(x: string | number | boolean) { if (typeof x === "string") { x; // type of x is string here x = 1; x; // type of x is number here } x; // type of x is number | boolean here } function bar(x: string | number) { if (typeof x === "number") { return; } x; // type of x is string here }

基于控制流的类型分析在--strictNullChecks模式中尤其相关,因为可空类型使用联合类型表示:

function test(x: string | null) { if (x === null) { return; } x; // type of x is string in remainder of function }

此外,在--strictNullChecks模式中,基于控制流的类型分析包括对不允许该undefined值的类型的局部变量的明确分配分析

function mumble(check: boolean) { let x: number; // Type doesn't permit undefined x; // Error, x is undefined if (check) { x = 1; x; // Ok } x; // Error, x is possibly undefined x = 2; x; // Ok }

标记的联合类型

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) { // In the following switch statement, the type of s is narrowed in each case clause // according to the value of the discriminant property, thus allowing the other properties // of that variant to be accessed without a type assertion. 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 == vx.p === vx.p != v,或x.p !== v,其中,pv是一个属性和一个字符串文字类型的表达式或字符串文字类型的联合。判别式p属性类型的保卫将x类型缩小为具有与其中一个可能v值相区别的属性的x那些组成类型。

请注意,我们目前仅支持字符串文字类型的判别属性。我们打算稍后添加对布尔和数字文字类型的支持。

never类型

TypeScript 2.0 引入了一种新的基元类型nevernever类型表示永远不会发生的值的类型。具体来说,never函数的返回类型是永远不会返回的,并且never是类型保卫下永远不会变为真的变量类型。

never类型具有以下特征:

  • never 是每种类型的一种子类型并可分配给每种类型。

  • 没有类型是一个子类型或可分配给never(除了never它本身)。

  • 在没有返回类型注释的函数表达式或箭头函数中,如果函数没有return语句,或者只有return具有类型表达式的语句never,并且如果函数的终点不可达(由控制流分析确定),则推断函数的返回类型是never

  • 在具有显式never返回类型注释的函数中,所有return语句(如果有的话)都必须具有never类型的表达式,并且该函数的结束点不可访问。

由于never是每种类型的子类型,因此它总是从联合类型中省略,并且在函数返回类型推断中会被忽略,只要还有其他类型被返回。

返回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) { } }

一些使用函数返回never的例子:

// Inferred return type is number function move1(direction: "up" | "down") { switch (direction) { case "up": return 1; case "down": return -1; } return error("Should never get here" } // Inferred return type is number function move2(direction: "up" | "down") { return direction === "up" ? 1 : direction === "down" ? -1 : error("Should never get here" } // Inferred return type is 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声明的实体被认为是只读的。

  • 通过 ES2015 名称空间导入访问的实体被视为只读(例如foo.xfoo声明为只读时为只读import * as foo from "foo")。

示例

interface Point { readonly x: number; readonly y: number; } var p1: Point = { x: 10, y: 20 }; p1.x = 5; // Error, p1.x is read-only var p2 = { x: 1, y: 1 }; var p3: Point = p2; // Ok, read-only alias for p2 p3.x = 5; // Error, p3.x is read-only p2.x = 5; // Ok, but also changes p3.x because of aliasing

class Foo { readonly a = 1; readonly b: string; constructor() { this.b = "hello"; // Assignment permitted in constructor } }

let a: Array<number> = [0, 1, 2, 3, 4]; let b: ReadonlyArray<number> = a; b[5] = 5; // Error, elements are read-only b.push(5 // Error, no push method (because it mutates array) b.length = 3; // Error, length is read-only a = b; // Error, mutating methods are missing

指定this函数的类型

继续指定类或接口的this类型后,函数和方法现在可以声明this它们所期望的类型。

默认情况下,this函数内部的类型是any。从 TypeScript 2.0 开始,您可以提供一个明确的this参数。this参数是在参数列表中首先出现的伪参数:

function f(this: void) { // make sure `this` is unusable in this standalone function }

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) { // oops, used this here. using this callback would crash at runtime this.info = e.message; }; } let h = new Handler( uiElement.addClickListener(h.onClickBad // error!

--noImplicitThis

在 TypeScript 2.0 中还添加了一个新标志,用于标记this函数中的所有用法,而不用明确的类型注释。

全范围支持 tsconfig.json

全范围支持在这里!

Glob-like 文件模式支持两个属性"include""exclude"

示例

{ "compilerOptions": { "module": "commonjs", "noImplicitAny": true, "removeComments": true, "preserveConstEnums": true, "outFile": "../../built/local/tsc.js", "sourceMap": true }, "include": [ "src/**/*" ], "exclude": [ "node_modules", "**/*.spec.ts" ] }

支持的通配符是:

  • * 匹配零个或多个字符(不包括目录分隔符)

  • ? 匹配任何一个字符(不包括目录分隔符)

  • **/ 递归匹配任何子目录

如果全范围图案的段仅包括*.*包括在内,则仅与支持的扩展名的文件(例如.ts.tsx.d.ts在默认情况下.js.jsx如果allowJs被设置为真)。

如果"files""include"两者都未指定,则编译器默认为包括所有打字稿(.ts.d.ts.tsx)文件在含有目录和子目录,除了那些使用排除"exclude"属性。如果allowJs设置为true,JS文件(.js.jsx)也包含在内。

如果指定了"files"或者 "include"属性,编译器将会包含这两个属性包含的文件的联合。"outDir"除非通过"files"属性显式包含(即使指定了“ exclude”属性),否则始终排除使用编译器选项指定的目录中的文件。

包含的文件"include"可以使用"exclude"属性进行过滤。但是,显式使用该"files"属性的文件总是包含在内,无论如何"exclude"。未指定时,该"exclude"属性默认为排除node_modules,。bower_componentsjspm_packages目录

模块解析增强功能:BaseUrl(基础网址),路径映射,rootDirs(根Dirs) 和跟踪

TypeScript 2.0 提供了一组额外的模块解析 knops,以通知编译器在哪里可以找到给定模块的声明。

有关详细信息,请参阅模块解析文档

基本网址

在使用AMD模块加载程序的应用程序中,使用一个baseUrl 是常见的做法,其中模块在运行时“部署”到单个文件夹。所有使用非相对名称的模块导入都假定为相对于baseUrl

示例

{ "compilerOptions": { "baseUrl": "./modules" } }

现在输入"moduleA"会在./modules/moduleA中查找

import A from "moduleA";

路径映射

有时模块不直接位于 baseUrl 下。加载程序使用映射配置在运行时将模块名称映射到文件,请参阅 RequireJs 文档SystemJS 文档

TypeScript 编译器支持使用文件中的"paths"属性声明这种映射tsconfig.json

示例

例如,对模块的导入"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

速记环境模块声明

如果您在使用新模块之前不想花时间写出声明,现在可以使用速记声明快速入门。

declarations.d.ts

declare module "hot-new-module";

所有从速记模块导入的文件都将具有任何类型。

import x, {y} from "hot-new-module"; x(y

模块名称中的通配符

使用模块加载器扩展(例如 AMD SystemJS)导入非代码资源并不容易; 之前必须为每个资源定义环境模块声明。

TypeScript 2.0 支持使用通配符(*)来声明模块名称的“族”; 这样,一个扩展只需要一次声明,而不是每个资源。

示例

declare module "*!text" { const content: string; export default content; } // Some do it the other way around. 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 is 'any'

支持 UMD 模块定义

一些库被设计用于许多模块加载器,或者没有模块加载(全局变量)。这些被称为 UMD 同构 模块。这些库可以通过导入或全局变量来访问。

例如:

math-lib.d.ts

export const isPrime(x: number): boolean; export as namespace mathLib;

该库然后可以用作模块内的导入:

import { isPrime } from "math-lib"; isPrime(2 mathLib.isPrime(2 // ERROR: can't use the global definition from inside a module

它也可以用作全局变量,但只能在脚本中使用。(脚本是一个没有导入或导出的文件。)

mathLib.isPrime(2

可选的类属性

现在可以在类中声明可选的属性和方法,类似于已经在接口中允许的内容。

示例

class Bar { a: number; b?: number; f() { return 1; } g?(): number; // Body of optional method can be omitted 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 }

私人和受保护的构造函数

类构造函数可能被标记privateprotected。具有私有构造函数的类不能在类体外实例化,并且不能扩展。具有受保护构造函数的类不能在类体外实例化,但可以扩展。

示例

class Singleton { private static instance: Singleton; private constructor() { } static getInstance() { if (!Singleton.instance) { Singleton.instance = new Singleton( } return Singleton.instance; } } let e = new Singleton( // Error: constructor of 'Singleton' is private. 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" } // Ok httpService("", headers // Now ok, previously wasn't

包含内置类型声明 --lib

获得 ES6 / ES2015 内置 API 声明仅限于target: ES6。输入--lib; 与--lib您可以指定您可以选择包含在您的项目中的内置API声明组列表。例如,如果你期望你的运行时支持MapSet并且Promise(例如今天最常见的浏览器),那就包括--lib es2015.collection,es2015.promise。同样,您可以排除不希望包含在项目中的声明,例如,如果正在使用节点项目,则使用DOM --lib es5,es6

以下是可用 API 组的列表:

  • dom

  • webworker

  • es5

  • es6 / es2015

  • es2015.core

  • es2015.collection

  • es2015.iterable

  • es2015.promise

  • es2015.proxy

  • es2015.reflect

  • es2015.generator

  • es2015.symbol

  • es2015.symbol.wellknown

  • es2016

  • es2016.array.include

  • es2017

  • es2017.object

  • es2017.sharedmemory

  • scripthost

示例

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"; // ^ Error: `B` declared but never used readFile( export function write(message: string, args: string[]) { // ^^^^ Error: 'arg' declared but never used. console.log(message }

名称以参数开头的参数声明_可免除未使用的参数检查。例如:

function returnNull(_a) { // OK return null; }

模块标识符允许.js扩展

在 TypeScript 2.0 之前,模块标识符总是被认为是无扩展的; 例如,在给定导入的情况下,import d from "./moduleA.js"编译器查找"moduleA.js"in ./moduleA.js.ts或的定义./moduleA.js.d.ts。这使得很难使用像 SystemJS 这样的捆绑/加载工具,它们的模块标识符需要 URI。

使用 TypeScript 2.0,编译器将查找"moduleA.js"./moduleA.ts./moduleA.d.t的定义。

使用 'module:es6' 支持 'target:es5'

以前标记为无效标志组合,现在支持target: es5和 'module:es6'。这应该有助于使用基于 ES2015 的树形图振动器。

函数参数和参数列表中的尾随逗号

函数参数和参数列表中的尾随逗号现在是允许的。这是 Stage-3 ECMAScript 提案的一个实现,发布到有效的 ES3 / ES5 / ES6。

示例

function foo( bar: Bar, baz: Baz, // trailing commas are OK in parameter lists ) { // Implementation... } foo( bar, baz, // and in argument lists

新 --skipLibCheck

TypeScript 2.0 添加了一个新的--skipLibCheck编译器选项,可以跳过声明文件(带扩展名的.d.ts文件)的类型检查。当程序包含大量声明文件时,编译器将花费大量时间类型检查声明,这些声明已知不包含错误,编译时间可能会因跳过声明文件类型检查而显着缩短。

由于一个文件中的声明会影响其他文件中的类型检查,因此指定--skipLibCheck因此指定时可能不会检测到某些错误。例如,如果非声明文件扩充了声明文件中声明的类型,则可能会导致仅在检查声明文件时才报告的错误。但是,实际上这种情况很少见。

在整个声明中允许重复标识符

这是重复定义错误的一个常见原因。多个声明文件在接口上定义相同的成员。

TypeScript 2.0 放宽了这个约束,并且允许跨块的重复标识符,只要它们具有相同的类型。

在同一个块内,重复的定义仍然被禁止。

示例

interface Error { stack?: string; } interface Error { code?: string; path?: string; stack?: string; // OK }

新 --declarationDir

--declarationDir 允许在与 JavaScript 文件不同的位置生成声明文件。