TypeScript 2.0

Null- and undefined-aware types

TypeScript具有两种特殊类型,即Null和Undefined,它们分别具有值nullundefined . 以前,不可能显式命名这些类型,但是无论类型检查模式如何,现在都可以将nullundefined用作类型名称.

类型检查器以前认为是null并且undefined可分配给任何对象. 实际上, nullundefined每种类型的有效值,因此不可能专门排除它们(因此不可能检测到它们的错误使用).

--strictNullChecks

--strictNullChecks切换到新的严格null检查模式.

在严格的null检查模式下, nullundefined不在每种类型的域中,并且只能分配给它们自己和any (一个例外是undefined也可以分配给void ). 因此,而TT | undefined T | undefined在常规类型检查模式下被视为同义词(因为undefined被视为任何T的子类型),在严格类型检查模式下它们是不同的类型,只有T | undefined T | undefined允许undefined值. TT | null的关系也是如此. T | null .

Example
ts
// 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

Assigned-before-use checking

在严格的空检查模式下,编译器要求对每个不包含undefined类型的局部变量的引用都必须在每个可能的在前代码路径中以对该变量的赋值为后缀.

Example
ts
// 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

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

Optional parameters and properties

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

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

Non-null and non-undefined type guards

如果对象或函数的类型包括nullundefined ,则属性访问或函数调用会产生编译时错误. 但是,类型防护措施已扩展为支持非null和非不确定检查.

Example
ts
// 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和非不确定类型的类型保护可以使用==!====!==运算符与nullundefined进行比较,例如x != nullx === undefined . 对主题变量类型的影响准确地反映了JavaScript语义(例如,无论指定了哪个值,双等号运算符都会检查这两个值,而三等号只会检查指定的值).

Dotted names in type guards

类型防护以前只支持检查局部变量和参数. 类型防护现在支持检查"点名",该"点名"由变量或参数名组成,然后是一个或多个属性访问.

Example
ts
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 } }

Type guards for dotted names also work with user defined type guard functions and the typeof and instanceof operators and do not depend on the --strictNullChecks compiler option.

分配给点名的任何部分后,带点名的类型防护将无效. 例如,在分配给xxyxyz之后, xyz的类型保护将无效.

Expression operators

表达式运算符允许操作数类型包括null和/或undefined但始终会产生非null和non-undefined类型的值.

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

&&运算符根据左操作数类型中存在的值和||向右操作数类型添加null和/或undefined 运算符从结果联合类型的左侧操作数的类型中删除nullundefined .

ts
// 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

Type widening

在严格的null检查模式下, nullundefined类型不会扩展为any类型.

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

在常规类型检查模式下, z的推断类型由于加宽而为any ,但在严格的null检查模式下, z的推断类型为null (因此,如果没有类型注释,则nullz的唯一可能值).

Non-null assertion operator

一个新的! 在类型检查器无法得出结论的情况下,可以使用后缀表达式运算符来断言其操作数是非null且未定义的. 具体来说,就是操作x! 生成x类型的值,其中排除了nullundefined . 类似于形式为<T>xx as T类型断言,即x as T ! 非null断言运算符仅在发出的JavaScript代码中删除.

ts
// 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 }

Compatibility

新功能的设计使其可以在严格的null检查模式和常规类型检查模式下使用. 特别是,在常规类型检查模式下,会从联合类型中自动删除nullundefined类型的类型(因为它们是所有其他类型的子类型),而!! 允许使用非null断言表达式运算符,但在常规类型检查模式下无效. 因此,为了向后兼容,仍可以在常规类型检查模式下使用更新为使用可识别null和undefined的类型的声明文件.

In practical terms, strict null checking mode requires that all files in a compilation are null- and undefined-aware.

Control flow based type analysis

TypeScript 2.0对局部变量和参数实施基于控制流的类型分析. 以前,对类型防护执行的类型分析仅限于if语句和?:条件表达式,并且不包括赋值和控制流构造(如returnbreak语句)的影响. 使用TypeScript 2.0,类型检查器会分析语句和表达式中所有可能的控制流,以在声明的任何给定位置为声明为具有并集类型的局部变量或参数生成最具体的类型( 缩小的类型 ).

Example
ts
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模式下特别重要,因为可空类型使用联合类型表示:

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

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

ts
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 }

Tagged union types

TypeScript 2.0实现对标记(或区分)联合类型的支持. 具体来说,TS编译器现在支持类型保护,该类型保护根据区分属性的测试来缩小联合类型,并进一步扩展了switch语句的能力.

Example
ts
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 }

判别属性类型保护是形式为xp == vxp === vxp != vxp !== v的表达式,其中pv是属性以及字符串文字类型或字符串文字类型的并集. 判别属性型保护变窄的类型x到的那些构成类型x具有的判别属性p与的可能值之一v .

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

The never type

TypeScript 2.0引入了一个never的新原始类型. never类型表示永不出现的值的类型. 具体而言, never是函数从不返回返回类型和never是下型后卫是从来没有真正的变量类型.

never类型具有以下特征:

  • never是每种类型的子类型,并且可以分配给每种类型.
  • 任何类型都不是never的子类型或可分配给never的子类型( never自身除外).
  • 在没有返回类型批注的函数表达式或箭头函数中,如果函数没有return语句,或者仅return带有类型为never表达式的return语句,并且函数的终点不可到达(由控制流分析确定) ,该函数的推断返回类型never .
  • 在具有显式never返回类型注释的函数中,所有return语句(如果有)都必须具有never类型的表达式,并且函数的终点必须不可访问.

因为never每种类型的子类型,所以在联合类型中始终将其忽略,只要返回其他类型,就在函数返回类型推断中将其忽略.

函数never返回的一些示例:

ts
// 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返回的函数的一些示例:

ts
// 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返回的函数:

ts
function test(cb: () => string) { let s = cb(); return s; } test(() => "hello"); test(() => fail()); test(() => { throw new Error(); })

Read-only properties and index signatures

现在可以使用readonly修饰符声明属性或索引签名,将其视为只读.

只读属性可能具有初始化程序,并且可以在同一类声明中的构造函数中分配给只读属性,但否则不允许分配只读属性.

此外,在以下几种情况下,实体是隐式只读的:

  • 使用get访问器声明且没有set访问器声明的属性被视为只读.
  • 在枚举对象的类型中,枚举成员被视为只读属性.
  • In the type of a module object, exported const variables are considered read-only properties.
  • import语句中声明的实体被视为只读.
  • 通过ES2015名称空间导入访问的实体被认为是只读的(例如,当foo被声明为import * as foo from "foo"时, foo.x是只读的).
Example
ts
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
ts
class Foo { readonly a = 1; readonly b: string; constructor() { this.b = "hello"; // Assignment permitted in constructor } }
ts
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

Specifying the type of this for functions

上指定的类型跟进this类或接口,函数和方法现在可以声明的类型, this他们的期望.

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

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

this parameters in callbacks

库还可以使用this参数来声明如何调用回调.

Example
ts
interface UIElement { addClickListener(onclick: (this: void, e: Event) => void): void; }

this: void表示addClickListener期望onclick是不需要this类型的函数.

现在,如果你做批注调用的代码this

ts
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功能的所有使用.

Glob support in tsconfig.json

全球支持在这里! 全球支持一直是最受要求的功能之一 .

支持glob-like文件模式的两个属性"include""exclude" .

Example
json
{ "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设置为true) .

如果"files""include"均未指定,则编译器默认将所有TypeScript文件( .ts.d.ts.tsx )包含在包含目录和子目录中,但使用"exclude"属性排除的文件除外. 如果allowJs设置为true,那么还将包括JS文件( .js.jsx ).

如果指定了"files""include"属性,则编译器将改为包含这两个属性所包含的文件的并集. 除非通过"files"属性明确包含(即使指定了" exclude "属性),否则始终排除使用"outDir"编译器选项指定的目录中的"files" .

可以使用"exclude"属性过滤使用"include"文件. 但是,无论是否使用"exclude"总是始终包含使用"files"属性明确包含的"files" . 未指定时, "exclude"属性默认排除node_modulesbower_componentsjspm_packages目录.

Module resolution enhancements: BaseUrl, Path mapping, rootDirs and tracing

TypeScript 2.0提供了一组附加的模块解析结点,以告知编译器在何处查找给定模块的声明.

有关更多详细信息,请参见模块分辨率文档.

Base URL

在使用AMD模块加载器的应用程序中,使用baseUrl是常见的做法,在这些应用程序中,模块在运行时被"部署"到单个文件夹中. 假定所有具有非相对名称的模块导入都相对于baseUrl .

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

现在,将在./modules/moduleA查找导入到"moduleA"./modules/moduleA

ts
import A from "moduleA";

Path mapping

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

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

Example

例如,导入到模块"jquery"将在运行时转换为"node_modules/jquery/dist/jquery.slim.min.js" .

json
{ "compilerOptions": { "baseUrl": "./node_modules", "paths": { "jquery": ["jquery/dist/jquery.slim.min"] } }

使用"paths"还可以进行更复杂的映射,包括多个后备位置. 考虑一个项目配置,在该配置中,一个位置只有一些模块可用,而其余的则位于另一个位置.

Virtual Directories with rootDirs

使用" rootDirs",您可以告知编译器组成该"虚拟"目录的目录. 因此,编译器可以解析这些"虚拟"目录中的相关模块导入, 就好像合并到一个目录中一样.

Example

鉴于此项目结构:

 src
 └── views
     └── view1.ts (imports './template1')
     └── view2.ts

 generated
 └── templates
         └── views
             └── template1.ts (imports './view2')

构建步骤将把/src/views/generated/templates/views的文件复制到输出中的同一目录. 在运行时,视图可以期望其模板位于其旁边,因此应使用相对名称"./template"导入它.

"rootDirs"指定了目录,这些目录的内容预计将在运行时合并. 因此,按照我们的示例, tsconfig.json文件应如下所示:

json
{ "compilerOptions": { "rootDirs": [ "src/views", "generated/templates/views" ] } }

Tracing module resolution

--traceResolution提供了一种方便的方法来了解编译器如何解析模块.

shell
tsc --traceResolution

Shorthand ambient module declarations

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

declarations.d.ts
ts
declare module "hot-new-module";

速记模块中的所有导入将具有任何类型.

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

Wildcard character in module names

以前,使用模块加载程序扩展(例如AMDSystemJS )导入非代码资源并非易事; 以前,必须为每个资源定义一个环境模块声明.

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

Example
ts
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!*" .

ts
import fileContent from "./xyz.txt!text"; import data from "json!http://example.com/data.json"; console.log(data, fileContent);

从无类型代码库迁移时,通配符模块名称甚至会更加有用. 结合Shorthand环境模块声明,可以轻松地将一组模块声明为any .

Example
ts
declare module "myLibrary/*";

编译器将把所有导入到myLibrary下的任何模块的类型any视为any . 因此,请停止检查这些模块的形状或类型.

ts
import { readFile } from "myLibrary/fileSystem/readFile`; readFile(); // readFile is 'any'

Support for UMD module definitions

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

例如:

math-lib.d.ts
ts
export const isPrime(x: number): boolean; export as namespace mathLib;

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

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

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

ts
mathLib.isPrime(2);

Optional class properties

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

Example
ts
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 number | undefined并且上面的g方法的类型为(() => number) | undefined (() => number) | undefined . 类型防护可用于剥离类型的undefined部分:

ts
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 and Protected Constructors

类构造函数可以标记为privateprotected . 具有私有构造函数的类不能在类主体外部实例化,也不能扩展. 具有受保护的构造函数的类不能在类主体外部实例化,但可以扩展.

Example
ts
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 properties and accessors

抽象类可以声明抽象属性和/或访问器. 任何子类都需要声明抽象属性或将其标记为抽象. 抽象属性不能具有初始化程序. 抽象访问器不能具有主体.

Example
ts
abstract class Base { abstract name: string; abstract get value(); abstract set value(v: number); } class Derived extends Base { name = "derived"; value = 1; }

Implicit index signatures

如果对象文字中的所有已知属性都可分配给该索引签名,则现在可以将对象文字类型分配给具有索引签名的类型. 这样就可以将使用对象文字作为参数初始化的变量传递给需要映射或字典的函数:

ts
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

Including built-in type declarations with --lib

进入ES6 / ES2015内置API声明仅限于target: ES6 . 输入--lib ; 使用--lib您可以指定内置API声明组的列表,可以选择将其包含在项目中. 例如,如果您希望运行时支持MapSetPromise (例如,当今大多数常绿的浏览器),则只需添加--lib es2015.collection,es2015.promise . 同样,您可以排除不想包含在项目中的声明,例如,如果您正在使用--lib es5,es6在节点项目上工作,则可以排除DOM.

以下是可用的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
Example
bash
tsc --target es5 --lib es5,es2015.promise
json
"compilerOptions": { "lib": ["es5", "es2015.promise"] }

Flag unused declarations with --noUnusedParameters and --noUnusedLocals

TypeScript 2.0具有两个新标志,可帮助您维护干净的代码库. --noUnusedParameters标记任何未使用的函数或方法参数错误. --noUnusedLocals标记任何未使用的本地(未导出)声明,例如变量,函数,类,导入等.此外,在--noUnusedLocals下,类的未使用私有成员将被标记为错误.

Example
ts
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); }

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

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

Module identifiers allow for .js extension

在TypeScript 2.0之前,始终将模块标识符假定为无扩展名. 例如,假设import d from "./moduleA.js"导入了import d from "./moduleA.js" ,则编译器会在./moduleA.js.ts./moduleA.js.d.ts./moduleA.js.ts "moduleA.js"的定义. 这使得难以使用SystemJS这样的绑定/加载工具, 这些工具期望模块标识符中包含URI.

使用TypeScript 2.0,编译器将在./moduleA.ts./moduleA.dt./moduleA.ts "moduleA.js"定义.

Support ‘target : es5’ with ‘module: es6’

以前被标记为无效标志组合,现在支持target: es5和'module:es6'. 这将有助于使用基于ES2015树摇床样 .

Trailing commas in function parameter and argument lists

现在允许在函数参数和参数列表中使用逗号结尾. 这是第3阶段ECMAScript提案的实现,该提案可发出有效的ES3 / ES5 / ES6.

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

New --skipLibCheck

TypeScript 2.0添加了一个新的--skipLibCheck编译器选项,该选项导致跳过声明文件(扩展名为.d.ts文件)的类型检查. 当程序包含大的声明文件时,编译器会花费大量时间进行类型检查,这些声明已经众所周知不包含错误,并且跳过声明文件类型检查可能会大大缩短编译时间.

Since declarations in one file can affect type checking in other files, some errors may not be detected when --skipLibCheck is specified. For example, if a non-declaration file augments a type declared in a declaration file, errors may result that are only reported when the declaration file is checked. However, in practice such situations are rare.

Allow duplicate identifiers across declarations

这是重复定义错误的一种常见来源. 在接口上定义相同成员的多个声明文件.

TypeScript 2.0放宽了此约束,并允许跨块重复标识符,只要它们具有相同的类型即可.

在同一块内,仍然不允许重复定义.

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

New --declarationDir

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

TypeScript文档是一个开源项目. 通过发送请求请求 ❤帮助我们改善这些页面

此页面的贡献者:
MH 穆罕默德·黑格兹(52)
OT 奥塔·特罗克斯(12)
V 瓦伦丁(1)
3 +

上次更新时间:2020年8月7日