keyof 和查找类型
🌐 keyof and Lookup Types
在 JavaScript 中,API 需要属性名作为参数的情况相当普遍,但目前还无法表达这些 API 中出现的类型关系。
🌐 In JavaScript it is fairly common to have APIs that expect property names as parameters, but so far it hasn’t been possible to express the type relationships that occur in those APIs.
输入索引类型查询或 keyof;
索引类型查询 keyof T 会返回 T 的允许属性名称的类型。
keyof T 类型被视为 string 的子类型。
🌐 Enter Index Type Query or keyof;
An indexed type query keyof T yields the type of permitted property names for T.
A keyof T type is considered a subtype of string.
示例
🌐 Example
tsinterface 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
它的对应概念是 索引访问类型,也称为 查找类型。 在语法上,它们看起来与元素访问完全相同,但作为类型书写:
🌐 The dual of this is indexed access types, also called lookup types. Syntactically, they look exactly like an element access, but are written as types:
示例
🌐 Example
tstype P1 = Person["name"]; // stringtype P2 = Person["name" | "age"]; // string | numbertype P3 = string["charAt"]; // (pos: number) => stringtype P4 = string[]["push"]; // (...items: string[]) => numbertype P5 = string[][0]; // string
你可以将此模式与类型系统的其他部分一起使用,以获得类型安全的查找。
🌐 You can use this pattern with other parts of the type system to get type-safe lookups.
tsfunction 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"); // numberlet bar = getProperty(x, "bar"); // stringlet oops = getProperty(x, "wargarbl"); // Error! "wargarbl" is not "foo" | "bar"setProperty(x, "foo", "string"); // Error!, string expected number
映射类型
🌐 Mapped Types
一个常见的任务是将现有类型的每个属性都设为可选。假设我们有一个 Person:
🌐 One common task is to take an existing type and make each of its properties entirely optional.
Let’s say we have a Person:
tsinterface Person {name: string;age: number;location: string;}
部分版本如下:
🌐 A partial version of it would be:
tsinterface PartialPerson {name?: string;age?: number;location?: string;}
使用映射类型,PartialPerson 可以写成对类型 Person 的一种通用转换,如下所示:
🌐 with Mapped types, PartialPerson can be written as a generalized transformation on the type Person as:
tstype Partial<T> = {[P in keyof T]?: T[P];};type PartialPerson = Partial<Person>;
映射类型是通过取字面量类型的联合,然后为一个新的对象类型计算一组属性来生成的。它们类似于 Python 中的 列表推导式,但它们不是生成列表中的新元素,而是生成类型中的新属性。
🌐 Mapped types are produced by taking a union of literal types, and computing a set of properties for a new object type. They’re like list comprehensions in Python, but instead of producing new elements in a list, they produce new properties in a type.
除了 Partial 之外,映射类型还可以对类型进行许多有用的转换:
🌐 In addition to Partial, Mapped Types can express many useful transformations on types:
ts// 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 onetype Deferred<T> = {[P in keyof T]: Promise<T[P]>;};// Wrap proxies around properties of Ttype Proxify<T> = {[P in keyof T]: { get(): T[P]; set(v: T[P]): void };};
Partial、Readonly、Record 和 Pick
🌐 Partial, Readonly, Record, and Pick
Partial 和 Readonly,如前所述,是非常有用的结构。你可以用它们来描述一些常见的 JS 例程,例如:
tsfunction assign<T>(obj: T, props: Partial<T>): void;function freeze<T>(obj: T): Readonly<T>;
因此,它们现在默认包含在标准库中。
🌐 Because of that, they are now included by default in the standard library.
我们还将包括另外两种实用类型:Record 和 Pick。
🌐 We’re also including two other utility types as well: Record and Pick.
ts// From T pick a set of properties Kdeclare function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K>;const nameAndAgeOnly = pick(person, "name", "age"); // { name: string, age: number }
ts// For every properties K of type T, transform it to Ufunction 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 }
对象扩展和剩余操作
🌐 Object Spread and Rest
TypeScript 2.1 带来了对 ESnext 扩展和剩余参数 的支持。
🌐 TypeScript 2.1 brings support for ESnext Spread and Rest.
与数组展开类似,展开对象可以方便地获取浅拷贝:
🌐 Similar to array spread, spreading an object can be handy to get a shallow copy:
tslet copy = { ...original };
同样,你可以合并几个不同的对象。在下面的例子中,merged 将拥有来自 foo、bar 和 baz 的属性。
🌐 Similarly, you can merge several different objects.
In the following example, merged will have properties from foo, bar, and baz.
tslet merged = { ...foo, ...bar, ...baz };
你还可以覆盖现有属性并添加新属性:
🌐 You can also override existing properties and add new ones:
tslet obj = { x: 1, y: "string" };var newObj = { ...obj, z: 3, y: 4 }; // { x: number, y: number, z: number }
指定展开操作的顺序决定了最终对象中包含哪些属性;后面的展开中的属性会“覆盖”之前创建的属性。
🌐 The order of specifying spread operations determines what properties end up in the resulting object; properties in later spreads “win out” over previously created properties.
对象剩余表达式是对象展开的对偶,因为它们可以提取在元素解构时未拾取的任何额外属性:
🌐 Object rests are the dual of object spreads, in that they can extract any extra properties that don’t get picked up when destructuring an element:
tslet obj = { x: 1, y: 1, z: 1 };let { z, ...obj1 } = obj;obj1; // {x: number, y:number};
下级异步函数
🌐 Downlevel Async Functions
在 TypeScript 2.1 之前,这个功能是支持的,但仅限于针对 ES6/ES2015。TypeScript 2.1 将这一功能扩展到了 ES3 和 ES5 运行时,这意味着无论你使用什么环境,都可以自由地利用它。
🌐 This feature was supported before TypeScript 2.1, but only when targeting ES6/ES2015. TypeScript 2.1 brings the capability to ES3 and ES5 run-times, meaning you’ll be free to take advantage of it no matter what environment you’re using.
注意:首先,我们需要确保我们的运行时全局可用一个符合 ECMAScript 标准的
Promise。 这可能需要获取一个Promise的 polyfill,或者依赖你目标运行时中已有的实现。 我们还需要确保 TypeScript 知道Promise的存在,可以通过将你的lib选项设置为类似"dom", "es2015"或"dom", "es2015.promise", "es5"来实现
示例
🌐 Example
tsconfig.json
{"": {"": ["dom", "es2015.promise", "es5"]}}
dramaticWelcome.ts
tsfunction 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 引擎上编译和运行输出应该能够得到正确的结果。
🌐 Compiling and running the output should result in the correct behavior on an ES3/ES5 engine.
支持外部辅助库(tslib)
🌐 Support for external helpers library (tslib)
TypeScript 会注入一些辅助函数,例如用于继承的 __extends、用于对象字面量和 JSX 元素中扩展运算符的 __assign,以及用于异步函数的 __awaiter。
🌐 TypeScript injects a handful of helper functions such as __extends for inheritance, __assign for spread operator in object literals and JSX elements, and __awaiter for async functions.
之前有两个选项:
🌐 Previously there were two options:
- 在每个需要它们的文件中注入助手,或者
- 在
noEmitHelpers上完全没有帮手。
这两个选项都不尽如人意;将辅助工具打包到每个文件中,对于那些希望保持包体积较小的客户来说,是一个痛点。而不包括辅助工具,则意味着客户必须自己维护一个辅助工具库。
🌐 The two options left more to be desired; bundling the helpers in every file was a pain point for customers trying to keep their package size small. And not including helpers, meant customers had to maintain their own helpers library.
TypeScript 2.1 允许将这些文件一次性地添加到项目中的单独模块中,编译器将根据需要向这些文件触发导入操作。
🌐 TypeScript 2.1 allows for including these files in your project once in a separate module, and the compiler will emit imports to them as needed.
首先,安装 tslib 工具库:
🌐 First, install the tslib utility library:
shnpm install tslib
其次,使用 importHelpers 编译你的文件:
🌐 Second, compile your files using importHelpers:
shtsc --module commonjs --importHelpers a.ts
因此,给定以下输入,生成的 .js 文件将包含对 tslib 的导入,并使用其中的 __assign 辅助函数,而不是内联它。
🌐 So given the following input, the resulting .js file will include an import to tslib and use the __assign helper from it instead of inlining it.
tsexport const o = { a: 1, name: "o" };export const copy = { ...o };
js"use strict";var tslib_1 = require("tslib");exports.o = { a: 1, name: "o" };exports.copy = tslib_1.__assign({}, exports.o);
无类型导入
🌐 Untyped imports
TypeScript 传统上对如何导入模块要求非常严格。这是为了避免拼写错误并防止用户错误使用模块。
🌐 TypeScript has traditionally been overly strict about how you can import modules. This was to avoid typos and prevent users from using modules incorrectly.
然而,很多时候,你可能只想导入一个现有的模块,而这个模块可能没有自己的 .d.ts 文件。以前这是一个错误。从 TypeScript 2.1 开始,这变得容易多了。
🌐 However, a lot of the time, you might just want to import an existing module that may not have its own .d.ts file.
Previously this was an error.
Starting with TypeScript 2.1 this is now much easier.
在 TypeScript 2.1 中,你可以导入 JavaScript 模块而无需类型声明。如果存在类型声明(例如 declare module "foo" { ... } 或 node_modules/@types/foo),它仍然优先。
🌐 With TypeScript 2.1, you can import a JavaScript module without needing a type declaration.
A type declaration (such as declare module "foo" { ... } or node_modules/@types/foo) still takes priority if it exists.
在没有声明文件的模块中导入仍然会在 noImplicitAny 下被标记为错误。
🌐 An import to a module with no declaration file will still be flagged as an error under noImplicitAny.
示例
🌐 Example
ts// Succeeds if `node_modules/asdf/index.js` existsimport { x } from "asdf";
支持 --target ES2016、--target ES2017 和 --target ESNext
🌐 Support for --target ES2016, --target ES2017 and --target ESNext
TypeScript 2.1 支持三个新的目标值 --target ES2016、--target ES2017 和 --target ESNext。
🌐 TypeScript 2.1 supports three new target values --target ES2016, --target ES2017 and --target ESNext.
使用目标 --target ES2016 将指示编译器不转换 ES2016 特有的特性,例如 ** 运算符。
🌐 Using target --target ES2016 will instruct the compiler not to transform ES2016-specific features, e.g. ** operator.
同样,--target ES2017 将指示编译器不转换诸如 async/await 之类的 ES2017 特定特性。
🌐 Similarly, --target ES2017 will instruct the compiler not to transform ES2017-specific features like async/await.
--target ESNext 以最新支持的 ES 提议特性 为目标。
改进的 any 推断
🌐 Improved any Inference
以前,如果 TypeScript 无法确定一个变量的类型,它会选择 any 类型。
🌐 Previously, if TypeScript couldn’t figure out the type of a variable, it would choose the any type.
tslet x; // implicitly 'any'let y = []; // implicitly 'any[]'let z: any; // explicitly 'any'.
在 TypeScript 2.1 中,TypeScript 不再只是选择 any,而是会根据你后续实际分配的内容来推断类型。
🌐 With TypeScript 2.1, instead of just choosing any, TypeScript will infer types based on what you end up assigning later on.
只有在设置了noImplicitAny时才会启用此功能。
🌐 This is only enabled if noImplicitAny is set.
示例
🌐 Example
tslet 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();
现在也对空数组进行同样的跟踪。
🌐 The same sort of tracking is now also done for empty arrays.
一个没有类型注解且初始值为 [] 的变量被认为是隐式的 any[] 变量。然而,每一个后续的 x.push(value)、x.unshift(value) 或 x[n] = value 操作都会根据添加到变量中的元素来“演变”变量的类型。
🌐 A variable declared with no type annotation and an initial value of [] is considered an implicit any[] variable.
However, each subsequent x.push(value), x.unshift(value) or x[n] = value operation evolves the type of the variable in accordance with what elements are added to it.
tsfunction 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}
隐式 any 错误
🌐 Implicit any errors
这的一个很大好处是,当使用 noImplicitAny 运行时,你会看到 明显更少 的隐式 any 错误。只有当编译器无法在没有类型注解的情况下确定变量类型时,才会报告隐式 any 错误。
🌐 One great benefit of this is that you’ll see way fewer implicit any errors when running with noImplicitAny.
Implicit any errors are only reported when the compiler is unable to know the type of a variable without a type annotation.
示例
🌐 Example
tsfunction 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.}}
更好地推断字面量类型
🌐 Better inference for literal types
字符串、数字和布尔字面量类型(例如 "abc"、1 和 true)以前仅在存在显式类型注解时才会被推断。从 TypeScript 2.1 开始,字面量类型会对 const 变量和 readonly 属性始终进行推断。
🌐 String, numeric and boolean literal types (e.g. "abc", 1, and true) were previously inferred only in the presence of an explicit type annotation.
Starting with TypeScript 2.1, literal types are always inferred for const variables and readonly properties.
对于没有类型注解的 const 变量或 readonly 属性,其推断的类型是字面量初始化值的类型。
对于带有初始化值且没有类型注解的 let 变量、var 变量、参数或非 readonly 属性,其推断的类型是初始化值的扩展字面量类型。
对于字符串字面量类型,扩展类型为 string;对于数字字面量类型,为 number;对于 true 或 false,为 boolean;对于枚举字面量类型,则为包含它的枚举类型。
🌐 The type inferred for a const variable or readonly property without a type annotation is the type of the literal initializer.
The type inferred for a let variable, var variable, parameter, or non-readonly property with an initializer and no type annotation is the widened literal type of the initializer.
Where the widened type for a string literal type is string, number for numeric literal types, boolean for true or false and the containing enum for enum literal types.
示例
🌐 Example
tsconst c1 = 1; // Type 1const c2 = c1; // Type 1const c3 = "abc"; // Type "abc"const c4 = true; // Type trueconst c5 = cond ? 1 : "abc"; // Type 1 | "abc"let v1 = 1; // Type numberlet v2 = c2; // Type numberlet v3 = c3; // Type stringlet v4 = c4; // Type booleanlet v5 = c5; // Type number | string
通过显式类型注解可以控制字面量类型的扩展。具体来说,当一个字面量类型的表达式在没有类型注解的 const 位置被推断时,该 const 变量会被推断为扩展字面量类型。但当 const 位置有显式的字面量类型注解时,const 变量会得到非扩展的字面量类型。
🌐 Literal type widening can be controlled through explicit type annotations.
Specifically, when an expression of a literal type is inferred for a const location without a type annotation, that const variable gets a widening literal type inferred.
But when a const location has an explicit literal type annotation, the const variable gets a non-widening literal type.
示例
🌐 Example
tsconst c1 = "hello"; // Widening type "hello"let v1 = c1; // Type stringconst c2: "hello" = "hello"; // Type "hello"let v2 = c2; // Type "hello"
将 super 调用返回的值作为 ‘this’ 使用
🌐 Use returned values from super calls as ‘this’
在 ES2015 中,返回对象的构造函数会隐式地将 this 的值替换给 super() 的任何调用者。
因此,有必要捕获 super() 的任何潜在返回值,并将其替换为 this。
这一更改使得可以使用 Custom Elements,它利用这一点来使用用户编写的构造函数初始化浏览器分配的元素。
🌐 In ES2015, constructors which return an object implicitly substitute the value of this for any callers of super().
As a result, it is necessary to capture any potential return value of super() and replace it with this.
This change enables working with Custom Elements, which takes advantage of this to initialize browser-allocated elements with user-written constructors.
示例
🌐 Example
tsclass Base {x: number;constructor() {// return a new object other than `this`return {x: 1};}}class Derived extends Base {constructor() {super();this.x = 2;}}
生成:
🌐 Generates:
jsvar 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等)的行为发生中断。有关详细信息,请参阅继承内置类的破坏性更改文档。
配置继承
🌐 Configuration inheritance
一个项目通常有多个输出目标,例如 ES5 和 ES2015,调试版和生产版,或者 CommonJS 和 System;这两个目标之间只有少数配置选项不同,而维护多个 tsconfig.json 文件可能会很麻烦。
🌐 Often a project has multiple output targets, e.g. ES5 and ES2015, debug and production or CommonJS and System;
Just a few configuration options change between these two targets, and maintaining multiple tsconfig.json files can be a hassle.
TypeScript 2.1 支持使用 extends 继承配置,其中:
🌐 TypeScript 2.1 supports inheriting configuration using extends, where:
extends是tsconfig.json中的新顶层属性(与compilerOptions、files、include和exclude并列)。extends的值必须是一个字符串,包含要继承的另一个配置文件的路径。- 首先加载基础文件中的配置,然后由继承配置文件中的配置覆盖。
- 配置文件之间不允许循环。
- 继承的配置文件中的
files、include和exclude会 覆盖 基础配置文件中的相应项。 - 配置文件中找到的所有相对路径都将相对于它们起源的配置文件进行解析。
示例
🌐 Example
configs/base.json:
{"": {"": true,"": true}}
tsconfig.json:
{"": "./configs/base","": ["main.ts", "supplemental.ts"]}
tsconfig.nostrictnull.json:
{"": "./tsconfig","": {"": false}}
新的 --alwaysStrict
🌐 New --alwaysStrict
使用 alwaysStrict 调用编译器会导致:
🌐 Invoking the compiler with alwaysStrict causes:
- 以严格模式解析所有代码。
- 在每个生成的文件顶部写入
"use strict";指令。
在严格模式下,模块会被自动解析。推荐在非模块代码中使用新的标志。
🌐 Modules are parsed automatically in strict mode. The new flag is recommended for non-module code.