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
Partial
和Readonly
如前所述,是非常有用的构造。你可以用它们来描述一些常见的 JS 例程,如:
function assign<T>(obj: T, props: Partial<T>): void;
function freeze<T>(obj: T): Readonly<T>;
因此,它们现在默认包含在标准库中。
我们还包括其他两种工具类型:Record
和Pick
。
// 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
将具有从属性foo
,bar
和baz
。
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"
,1
和true
)仅在存在显式类型注释的情况下才被推断。从 TypeScript 2.1
开始,文字类型总是被
推断为const
变量和readonly
属性。
为没有类型注释的const
变量或readonly
属性推断的类型是字面初始值设定项的类型。使用初始值设定项和无类型注释为let
变量,var
变量,参数或非readonly
属性推断的类型是初始值设定项的宽化文字类型。其中加宽的类型字符串文字类型是string
,number
对于数字文字类型,boolean
为true
或false
,并且枚举类型字面含枚举。
示例
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)
这种变化需要延长像
Error
,Array
,Map
,等内置类的行为休止符。请参阅扩展内建重大更改文档
了解更多详情。
配置继承
通常,一个项目有多个输出目标,例如ES5
和ES2015
,调试和生产,CommonJS
和System
; 只有几个配置选项在这两个目标之间切换,并且维护多个tsconfig.json
文件可能会很麻烦。
TypeScript 2.1 支持使用extends
继承配置,其中:
extends
是tsconfig.json
(旁边compilerOptions
,files
和include
,和exclude
)一个新的顶级财产。
- 值
extends
必须是包含要从其继承的另一个配置文件的路径的字符串。
- 基础文件中的配置首先被加载,然后被继承配置文件中的配置覆盖。
- 配置文件之间的循环不允许。
files
,include
和exclude
从继承的配置文件覆盖
基本配置文件中的那些。
- 在配置文件中找到的所有相对路径将相对于它们所在的配置文件进行解析。
示例
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";
在每个生成的文件上写入指令。
模块在严格模式下自动解析。新标志推荐用于非模块代码。