支持带有 keyof 和映射类型的 number 和 symbol 命名属性
🌐 Support number and symbol named properties with keyof and mapped types
TypeScript 2.9 在索引类型和映射类型中增加了对 number 和 symbol 命名属性的支持。之前,keyof 运算符和映射类型只支持 string 命名属性。
🌐 TypeScript 2.9 adds support for number and symbol named properties in index types and mapped types.
Previously, the keyof operator and mapped types only supported string named properties.
更改包括:
🌐 Changes include:
- 某种类型
T的索引类型keyof T是string | number | symbol的子类型。 - 映射类型
{ [P in K]: XXX }允许任何可分配给string | number | symbol的K。 - 在一个针对泛型类型
T对象的for...in语句中,迭代变量的推断类型以前是keyof T,但现在是Extract<keyof T, string>。(换句话说,仅包括类似字符串值的keyof T子集。)
对于一个对象类型 X,keyof X 的解析如下:
🌐 Given an object type X, keyof X is resolved as follows:
- 如果
X包含字符串索引签名,keyof X是string、number和表示类符号属性的字面量类型的联合类型,否则 - 如果
X包含一个数字索引签名,keyof X是number与表示类似字符串和符号属性的字面量类型的联合,否则 keyof X是表示类似字符串、类似数字和类似符号属性的字面量类型的联合类型。
其中:
🌐 Where:
- 对象类型的类似字符串的属性是使用标识符、字符串字面量或字符串字面量类型的计算属性名称声明的属性。
- 对象类型的类似数字的属性是使用数字字面量或数字字面量类型的计算属性名称声明的属性。
- 对象类型的类符号属性是使用唯一符号类型的计算属性名称声明的属性。
在映射类型 { [P in K]: XXX } 中,K 中的每个字符串字面量类型都会引入一个字符串名称的属性,K 中的每个数字字面量类型都会引入一个数字名称的属性,而 K 中的每个唯一符号类型都会引入一个唯一符号名称的属性。
此外,如果 K 包含类型 string,则会引入字符串索引签名;如果 K 包含类型 number,则会引入数字索引签名。
🌐 In a mapped type { [P in K]: XXX }, each string literal type in K introduces a property with a string name, each numeric literal type in K introduces a property with a numeric name, and each unique symbol type in K introduces a property with a unique symbol name.
Furthermore, if K includes type string, a string index signature is introduced, and if K includes type number, a numeric index signature is introduced.
示例
🌐 Example
tsconst c = "c";const d = 10;const e = Symbol();const enum E1 {A,B,C,}const enum E2 {A = "A",B = "B",C = "C",}type Foo = {a: string; // String-like name5: string; // Number-like name[c]: string; // String-like name[d]: string; // Number-like name[e]: string; // Symbol-like name[E1.A]: string; // Number-like name[E2.A]: string; // String-like name};type K1 = keyof Foo; // "a" | 5 | "c" | 10 | typeof e | E1.A | E2.Atype K2 = Extract<keyof Foo, string>; // "a" | "c" | E2.Atype K3 = Extract<keyof Foo, number>; // 5 | 10 | E1.Atype K4 = Extract<keyof Foo, symbol>; // typeof e
由于 keyof 现在通过在键类型中包含类型 number 来反映数字索引签名的存在,当将映射类型如 Partial<T> 和 Readonly<T> 应用于具有数字索引签名的对象类型时,它们能够正确工作:
🌐 Since keyof now reflects the presence of a numeric index signature by including type number in the key type, mapped types such as Partial<T> and Readonly<T> work correctly when applied to object types with numeric index signatures:
tstype Arrayish<T> = {length: number;[x: number]: T;};type ReadonlyArrayish<T> = Readonly<Arrayish<T>>;declare const map: ReadonlyArrayish<string>;let n = map.length;let x = map[123]; // Previously of type any (or an error with --noImplicitAny)
此外,随着 keyof 运算符对 number 和 symbol 命名键的支持,现在可以抽象化访问通过数字字面量(例如数字枚举类型)和唯一符号索引的对象属性。
🌐 Furthermore, with the keyof operator’s support for number and symbol named keys, it is now possible to abstract over access to properties of objects that are indexed by numeric literals (such as numeric enum types) and unique symbols.
tsconst enum Enum {A,B,C,}const enumToStringMap = {[Enum.A]: "Name A",[Enum.B]: "Name B",[Enum.C]: "Name C",};const sym1 = Symbol();const sym2 = Symbol();const sym3 = Symbol();const symbolToNumberMap = {[sym1]: 1,[sym2]: 2,[sym3]: 3,};type KE = keyof typeof enumToStringMap; // Enum (i.e. Enum.A | Enum.B | Enum.C)type KS = keyof typeof symbolToNumberMap; // typeof sym1 | typeof sym2 | typeof sym3function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {return obj[key];}let x1 = getValue(enumToStringMap, Enum.C); // Returns "Name C"let x2 = getValue(symbolToNumberMap, sym3); // Returns 3
这是一个重大更改;以前,keyof 操作符和映射类型仅支持 string 命名属性。假设 keyof T 类型的值总是 string 的代码,现在将被标记为错误。
🌐 This is a breaking change; previously, the keyof operator and mapped types only supported string named properties.
Code that assumed values typed with keyof T were always strings, will now be flagged as error.
示例
🌐 Example
tsfunction useKey<T, K extends keyof T>(o: T, k: K) {var name: string = k; // Error: keyof T is not assignable to string}
建议
🌐 Recommendations
-
如果你的函数只能处理字符串命名的属性键,在声明中使用
Extract<keyof T, string>:tsfunction useKey<T, K extends Extract<keyof T, string>>(o: T, k: K) {var name: string = k; // OK} -
如果你的函数可以处理所有属性键,则更改应在下游进行:
tsfunction useKey<T, K extends keyof T>(o: T, k: K) {var name: string | number | symbol = k;} -
否则使用
keyofStringsOnly编译器选项来禁用新行为。
JSX 元素中的泛型类型参数
🌐 Generic type arguments in JSX elements
JSX 元素现在允许将类型参数传递给泛型组件。
🌐 JSX elements now allow passing type arguments to generic components.
示例
🌐 Example
tsclass GenericComponent<P> extends React.Component<P> {internalProp: P;}type Props = { a: number; b: string };const x = <GenericComponent<Props> a={10} b="hi" />; // OKconst y = <GenericComponent<Props> a={10} b={20} />; // Error
泛型标记模板中的泛型类型参数
🌐 Generic type arguments in generic tagged templates
标签模板是 ECMAScript 2015 引入的一种调用形式。与调用表达式类似,泛型函数可以在标签模板中使用,TypeScript 会推断所使用的类型参数。
🌐 Tagged templates are a form of invocation introduced in ECMAScript 2015. Like call expressions, generic functions may be used in a tagged template and TypeScript will infer the type arguments utilized.
TypeScript 2.9 允许将泛型类型参数传递给带标签的模板字符串。
🌐 TypeScript 2.9 allows passing generic type arguments to tagged template strings.
示例
🌐 Example
tsdeclare function styledComponent<Props>(strs: TemplateStringsArray): Component<Props>;interface MyProps {name: string;age: number;}styledComponent<MyProps>`font-size: 1.5em;text-align: center;color: palevioletred;`;declare function tag<T>(strs: TemplateStringsArray, ...args: T[]): T;// inference fails because 'number' and 'string' are both candidates that conflictlet a = tag<string | number>`${100} ${"hello"}`;
import 类型
🌐 import types
模块可以导入在其他模块中声明的类型。但非模块的全局脚本无法访问在模块中声明的类型。这时候可以使用 import 类型。
🌐 Modules can import types declared in other modules. But non-module global scripts cannot access types declared in modules. Enter import types.
在类型注解中使用 import("mod") 可以在模块中访问其导出的声明而无需导入它。
🌐 Using import("mod") in a type annotation allows for reaching in a module and accessing its exported declaration without importing it.
示例
🌐 Example
在模块文件中给出类 Pet 的声明:
🌐 Given a declaration of a class Pet in a module file:
ts// module.d.tsexport declare class Pet {name: string;}
可以在非模块文件 global-script.ts 中使用:
🌐 Can be used in a non-module file global-script.ts:
ts// global-script.tsfunction adopt(p: import("./module").Pet) {console.log(`Adopting ${p.name}...`);}
这在 JSDoc 注释中也适用,用于引用 .js 中其他模块的类型:
🌐 This also works in JSDoc comments to refer to types from other modules in .js:
js// a.js/*** @param p { import("./module").Pet }*/function walk(p) {console.log(`Walking ${p.name}...`);}
放宽声明触发可见性规则
🌐 Relaxing declaration emit visibility rules
有了 import 类型,许多在声明文件生成期间报告的可见性错误可以由编译器处理,而无需更改输入。
🌐 With import types available, many of the visibility errors reported during declaration file generation can be handled by the compiler without the need to change the input.
例如:
🌐 For instance:
tsimport { createHash } from "crypto";export const hash = createHash("sha256");// ^^^^// Exported variable 'hash' has or is using name 'Hash' from external module "crypto" but cannot be named.
在 TypeScript 2.9 中,不会报告任何错误,现在生成的文件如下所示:
🌐 With TypeScript 2.9, no errors are reported, and now the generated file looks like:
tsexport declare const hash: import("crypto").Hash;
支持 import.meta
🌐 Support for import.meta
TypeScript 2.9 引入了对 import.meta 的支持,这是一种新的元属性,如当前的 TC39 提案 所描述。
🌐 TypeScript 2.9 introduces support for import.meta, a new meta-property as described by the current TC39 proposal.
import.meta 的类型是全局的 ImportMeta 类型,该类型定义在 lib.es5.d.ts 中。这个接口非常有限。要为 Node 或浏览器添加知名属性,可能需要接口合并,并且根据具体情况可能还需要全局扩展。
🌐 The type of import.meta is the global ImportMeta type which is defined in lib.es5.d.ts.
This interface is extremely limited.
Adding well-known properties for Node or browsers requires interface merging and possibly a global augmentation depending on the context.
示例
🌐 Example
假设 __dirname 总是在 import.meta 上可用,那么声明将通过重新打开 ImportMeta 接口来完成:
🌐 Assuming that __dirname is always available on import.meta, the declaration would be done through reopening ImportMeta interface:
ts// node.d.tsinterface ImportMeta {__dirname: string;}
用法如下:
🌐 And usage would be:
tsimport.meta.__dirname; // Has type 'string'
import.meta 仅在面向 ESNext 模块和 ECMAScript 目标时允许使用。
新的 --resolveJsonModule
🌐 New --resolveJsonModule
在 Node.js 应用中,经常需要一个 .json。使用 TypeScript 2.9,resolveJsonModule 允许导入、提取类型并生成 .json 文件。
🌐 Often in Node.js applications a .json is needed. With TypeScript 2.9, resolveJsonModule allows for importing, extracting types from and generating .json files.
示例
🌐 Example
ts// settings.json{"repo": "TypeScript","dry": false,"debug": false}
ts// a.tsimport settings from "./settings.json";settings.debug === true; // OKsettings.dry === 2; // Error: Operator '===' cannot be applied boolean and number
// tsconfig.json{"": {"": "commonjs","": true,"": true}}
--pretty 默认输出
🌐 --pretty output by default
如果输出设备支持彩色文本,TypeScript 2.9 的起始错误默认显示在 pretty 下。TypeScript 将检查输出流是否设置了 isTty 属性。
🌐 Starting TypeScript 2.9 errors are displayed under pretty by default if the output device is applicable for colorful text.
TypeScript will check if the output stream has isTty property set.
在命令行上使用 --pretty false,或在你的 tsconfig.json 中设置 "pretty": false 来禁用 pretty 输出。
🌐 Use --pretty false on the command line or set "pretty": false in your tsconfig.json to disable pretty output.
新的 --declarationMap
🌐 New --declarationMap
启用 declarationMap 与 declaration 一起会导致编译器在输出 .d.ts 文件的同时生成 .d.ts.map 文件。语言服务现在也可以理解这些映射文件,并在可用时使用它们将基于声明文件的定义位置映射到其原始源。
🌐 Enabling declarationMap alongside declaration causes the compiler to emit .d.ts.map files alongside the output .d.ts files.
Language Services can also now understand these map files, and uses them to map declaration-file based definition locations to their original source, when available.
换句话说,在一个由 declarationMap 生成的 .d.ts 文件中的声明上点击“转到定义”,会带你到该声明被定义的源文件(.ts)位置,而不是 .d.ts。
🌐 In other words, hitting go-to-definition on a declaration from a .d.ts file generated with declarationMap will take you to the source file (.ts) location where that declaration was defined, and not to the .d.ts.