TypeScript 2.1

TypeScript 2.1

keyof 和查找类型

在 JavaScript 中,使用期望属性名称作为参数的 API 是相当常见的,但到目前为止还无法表达这些 API 中发生的类型关系。

输入索引类型查询或keyof; 索引类型查询keyof T产生的许可属性名称的类型T。一个keyof T类型被认为是一个string子类型。

示例

interface Person { name: string; age: number; location: string; } type K1 = keyof Person; // "name" | "age" | "location" type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ... type K3 = keyof { [x: string]: Person }; // string

这是双重索引访问类型,也称为查找类型。在语法上,它们看起来完全像一个元素访问,但是写成类型:

示例

type P1 = Person["name"]; // string type P2 = Person["name" | "age"]; // string | number type P3 = string["charAt"]; // (pos: number) => string type P4 = string[]["push"]; // (...items: string[]) => number type P5 = string[][0]; // string

您可以将此模式与类型系统的其他部分一起使用,以获得类型安全的查找。

function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]; // Inferred type is T[K] } function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]) { obj[key] = value; } let x = { foo: 10, bar: "hello!" }; let foo = getProperty(x, "foo" // number let bar = getProperty(x, "bar" // string let oops = getProperty(x, "wargarbl" // Error! "wargarbl" is not "foo" | "bar" setProperty(x, "foo", "string" // Error!, string expected number

映射类型

一个常见的任务是采取现有的类型,并使其每个属性完全可选。假设我们有一个Person

interface Person { name: string; age: number; location: string; }

它的部分版本将是:

interface PartialPerson { name?: string; age?: number; location?: string; }

与 Mapped 类型一样,PartialPerson可以写成Person类型的广义转换:

type Partial<T> = { [P in keyof T]?: T[P]; }; type PartialPerson = Partial<Person>;

映射类型是通过采用文字类型的联合而产生的,并为新的对象类型计算一组属性。它们就像 Python 中的列表推导,但不是在列表中产生新的元素,而是在一个类型中产生新的属性。

Partial之外,映射类型可以表示许多有用的类型转换:

// Keep types the same, but make each property to be read-only. type Readonly<T> = { readonly [P in keyof T]: T[P]; }; // Same property names, but make the value a promise instead of a concrete one type Deferred<T> = { [P in keyof T]: Promise<T[P]>; }; // Wrap proxies around properties of T type Proxify<T> = { [P in keyof T]: { get(): T[P]; set(v: T[P]): void } };

Partial,Readonly,Record,和Pick

PartialReadonly如前所述,是非常有用的构造。你可以用它们来描述一些常见的 JS 例程,如:

function assign<T>(obj: T, props: Partial<T>): void; function freeze<T>(obj: T): Readonly<T>;

因此,它们现在默认包含在标准库中。

我们还包括其他两种工具类型:RecordPick

// From T pick a set of properties K declare function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K>; const nameAndAgeOnly = pick(person, "name", "age" // { name: string, age: number }

// For every properties K of type T, transform it to U function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U> const names = { foo: "hello", bar: "world", baz: "bye" }; const lengths = mapObject(names, s => s.length // { foo: number, bar: number, baz: number }

对象传播和其他

TypeScript 2.1 支持 ESnext Spread 和 Rest

类似于数组传播,传播对象可以很方便地获得浅拷贝:

let copy = { ...original };

同样,您可以合并几个不同的对象。在以下示例中,merged将具有从属性foobarbaz

let merged = { ...foo, ...bar, ...baz };

您也可以覆盖现有的属性并添加新的属性:

let obj = { x: 1, y: "string" }; var newObj = {...obj, z: 3, y: 4}; // { x: number, y: number, z: number }

指定传播操作的顺序决定了结果对象中的特性;在以后的价差中的房产“胜出”以前创建的房产。

对象休止符(rests )是对象传播的双重对象,因为它们可以提取任何在解构元素时无法获得的额外属性:

let obj = { x: 1, y: 1, z: 1 }; let { z, ...obj1 } = obj; obj1; // {x: number, y:number};

低级异步函数

此功能在 TypeScript 2.1 之前受支持,但仅在定位 ES6 / ES2015 时支持。TypeScript 2.1 带来了 ES3 和 ES5 运行时间的功能,这意味着无论您使用何种环境,您都可以自由利用它。

注意:首先,我们需要确保我们的运行时Promise在全球范围内具有符合 ECMAScript 的兼容性。这可能涉及抓住一个填充工具Promise,或者依靠一个你可能在你所定位的运行时间。我们还需要确保 TypeScript 通过将lib标志设置为"dom", "es2015"或类似"dom", "es2015.promise", "es5"的方式来知道Promise存在 。

示例

tsconfig.json

{ "compilerOptions": { "lib": ["dom", "es2015.promise", "es5"] } }

dramaticWelcome.ts

function delay(milliseconds: number) { return new Promise<void>(resolve => { setTimeout(resolve, milliseconds } } async function dramaticWelcome() { console.log("Hello" for (let i = 0; i < 3; i++) { await delay(500 console.log("." } console.log("World!" } dramaticWelcome(

编译和运行输出应该在 ES3 / ES5 引擎上产生正确的行为。

支持外部助手库(tslib)

TypeScript 注入了一些辅助函数,如__extends继承,__assign对象文本和 JSX 元素中的扩展运算符以及__awaiter异步函数。

以前有两种选择:

  • 每个需要它们的文件中注入助手,或者

2. 根本没有帮手--noEmitHelpers

这两种选择还有待改进;捆绑在每个文件中的助手对于试图保持其包装尺寸小的客户来说是一个痛点。不包括助手,意味着客户必须维护他们自己的助手库。

TypeScript 2.1 允许在一个单独的模块中将这些文件包含在项目中,并且编译器将根据需要向它们发出导入。

首先,安装tslib实用程序库:

npm install tslib

其次,编译你的文件使用--importHelpers

tsc --module commonjs --importHelpers a.ts

因此,在给出以下输入的情况下,生成的.js文件将包含一个导入tslib和从中使用__assign帮助器,而不是内联它。

export const o = { a: 1, name: "o" }; export const copy = { ...o };

"use strict"; var tslib_1 = require("tslib" exports.o = { a: 1, name: "o" }; exports.copy = tslib_1.__assign{}, exports.o

非类型化的导入

传统上,TypeScript 对于如何导入模块过于严格。这是为了避免错别字,并防止用户错误地使用模块。

但是,很多时候,您可能只想导入可能没有自己.d.ts文件的现有模块。以前这是一个错误。从 TypeScript 2.1 开始,现在更容易了。

使用 TypeScript 2.1,您可以导入 JavaScript 模块而不需要类型声明。类型声明(如declare module "foo" { ... }or node_modules/@types/foo)仍然优先(如果存在)。

没有声明文件的模块导入--noImplicitAny仍然会被标记为错误。

示例

// Succeeds if `node_modules/asdf/index.js` exists import { x } from "asdf";

支持--target ES2016,--target ES2017和--target ESNext

TypeScript 2.1 支持三个新的目标值--target ES2016--target ES2017--target ESNext

使用目标--target ES2016将指示编译器不要转换 ES2016 特定的功能,例如**运算符。

同样,--target ES2017将指示编译器像async/await不要转换 / ES2017 特定的功能。

--target ESNext针对最新支持的 ES 建议功能

改进的any推论

以前,如果 TypeScript 无法确定变量的类型,它会选择any类型。

let x; // implicitly 'any' let y = []; // implicitly 'any[]' let z: any; // explicitly 'any'.

使用 TypeScript 2.1,TypeScript 将根据最终分配的内容推断类型,而不是仅仅选择any

这仅在--noImplicitAny设置时才能启用。

示例

let x; // You can still assign anything you want to 'x'. x = () => 42; // After that last assignment, TypeScript 2.1 knows that 'x' has type '() => number'. let y = x( // Thanks to that, it will now tell you that you can't add a number to a function! console.log(x + y // ~~~~~ // Error! Operator '+' cannot be applied to types '() => number' and 'number'. // TypeScript still allows you to assign anything you want to 'x'. x = "Hello world!"; // But now it also knows that 'x' is a 'string'! x.toLowerCase(

现在也对空数组进行相同类型的跟踪。

声明为没有类型注释并且初始值为的变量[]被认为是隐式any[]变量。然而,每个随后x.push(value)x.unshift(value)或者x[n] = value操作演变按照什么元素被添加到它的变量的类型。

function f1() { let x = []; x.push(5 x[1] = "hello"; x.unshift(true return x; // (string | number | boolean)[] } function f2() { let x = null; if (cond()) { x = []; while (cond()) { x.push("hello" } } return x; // string[] | null }

隐含任何错误

这样做的一大好处是,运行--noImplicitAny时会看到更少的隐式any错误。只有当编译器无法知道没有类型注释的变量类型时,才会报告隐含any错误。

示例

function f3() { let x = []; // Error: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined. x.push(5 function g() { x; // Error: Variable 'x' implicitly has an 'any[]' type. } }

更好地推理字面类型

字符串,数字和布尔文字类型(例如"abc"1true)仅在存在显式类型注释的情况下才被推断。从 TypeScript 2.1 开始,文字类型总是被推断为const变量和readonly属性。

为没有类型注释的const变量或readonly属性推断的类型是字面初始值设定项的类型。使用初始值设定项和无类型注释为let变量,var变量,参数或非readonly属性推断的类型是初始值设定项的宽化文字类型。其中加宽的类型字符串文字类型是stringnumber对于数字文字类型,booleantruefalse,并且枚举类型字面含枚举。

示例

const c1 = 1; // Type 1 const c2 = c1; // Type 1 const c3 = "abc"; // Type "abc" const c4 = true; // Type true const c5 = cond ? 1 : "abc"; // Type 1 | "abc" let v1 = 1; // Type number let v2 = c2; // Type number let v3 = c3; // Type string let v4 = c4; // Type boolean let v5 = c5; // Type number | string

文字类型加宽可以通过显式类型注释来控制。具体来说,当一个常量位置的文字类型表达式被推断出来而没有一个类型注释时,该const变量就会推断出一个加宽的文字类型。但是,如果某个const位置具有明确的文字类型注释,则该const变量将获得非加宽文字类型。

示例

const c1 = "hello"; // Widening type "hello" let v1 = c1; // Type string const c2: "hello" = "hello"; // Type "hello" let v2 = c2; // Type "hello"

将超级调用的返回值用作'this'

在 ES2015 中,返回对象的构造函数隐式地替换this任何调用者的值super()。因此,有必要捕捉任何潜在的回报值super()并用其代替this。此更改支持使用自定义元素,利用此元素可以使用用户编写的构造函数初始化浏览器分配的元素。

示例

class Base { x: number; constructor() { // return a new object other than `this` return { x: 1, }; } } class Derived extends Base { constructor() { super( this.x = 2; } }

产生:

var Derived = (function (_super) { __extends(Derived, _super function Derived() { var _this = _super.call(this) || this; _this.x = 2; return _this; } return Derived; }(Base)

这种变化需要延长像ErrorArrayMap,等内置类的行为休止符。请参阅扩展内建重大更改文档了解更多详情。

配置继承

通常,一个项目有多个输出目标,例如ES5ES2015,调试和生产,CommonJSSystem; 只有几个配置选项在这两个目标之间切换,并且维护多个tsconfig.json文件可能会很麻烦。

TypeScript 2.1 支持使用extends继承配置,其中:

  • extendstsconfig.json(旁边compilerOptionsfilesinclude,和exclude)一个新的顶级财产。

  • extends必须是包含要从其继承的另一个配置文件的路径的字符串。

  • 基础文件中的配置首先被加载,然后被继承配置文件中的配置覆盖。

  • 配置文件之间的循环不允许。

  • filesincludeexclude从继承的配置文件覆盖基本配置文件中的那些。

  • 在配置文件中找到的所有相对路径将相对于它们所在的配置文件进行解析。

示例

configs/base.json:

{ "compilerOptions": { "noImplicitAny": true, "strictNullChecks": true } }

tsconfig.json:

{ "extends": "./configs/base", "files": [ "main.ts", "supplemental.ts" ] }

tsconfig.nostrictnull.json:

{ "extends": "./tsconfig", "compilerOptions": { "strictNullChecks": false } }

新 --alwaysStrict

--alwaysStrict原因调用编译器:

  • 以严格模式解析所有代码。

2. "use strict";在每个生成的文件上写入指令。

模块在严格模式下自动解析。新标志推荐用于非模块代码。