使用 --incremental 标志可加快后续构建速度
🌐 Faster subsequent builds with the --incremental flag
TypeScript 3.4 引入了一个名为 incremental 的新标志,它告诉 TypeScript 保存上一次编译的项目图信息。下一次使用 incremental 调用 TypeScript 时,它将使用这些信息来检测对项目进行类型检查和输出更改的成本最低方式。
🌐 TypeScript 3.4 introduces a new flag called incremental which tells TypeScript to save information about the project graph from the last compilation.
The next time TypeScript is invoked with incremental, it will use that information to detect the least costly way to type-check and emit changes to your project.
// tsconfig.json{"": {"": true,"": "./lib"},"": ["./src"]}
根据这些设置的默认情况,当我们运行 tsc 时,TypeScript 会在输出目录(./lib)中查找一个名为 .tsbuildinfo 的文件。如果 ./lib/.tsbuildinfo 不存在,它将被生成。但如果存在,tsc 将尝试使用该文件进行增量类型检查并更新我们的输出文件。
🌐 By default with these settings, when we run tsc, TypeScript will look for a file called .tsbuildinfo in the output directory (./lib).
If ./lib/.tsbuildinfo doesn’t exist, it’ll be generated.
But if it does, tsc will try to use that file to incrementally type-check and update our output files.
这些 .tsbuildinfo 文件可以安全删除,对我们的代码在运行时没有任何影响——它们纯粹是用来加快编译速度的。
我们也可以随意命名它们,并使用 tsBuildInfoFile 选项将它们放在任何地方。
🌐 These .tsbuildinfo files can be safely deleted and don’t have any impact on our code at runtime - they’re purely used to make compilations faster.
We can also name them anything that we want, and place them anywhere we want using the tsBuildInfoFile option.
// front-end.tsconfig.json{"": {"": true,"": "./buildcache/front-end","": "./lib"},"": ["./src"]}
复合项目
🌐 Composite projects
复合项目(tsconfig.json 并且 composite 设置为 true)的部分目的在于,不同项目之间的引用可以增量构建。
因此,复合项目总是会生成 .tsbuildinfo 文件。
🌐 Part of the intent with composite projects (tsconfig.jsons with composite set to true) is that references between different projects can be built incrementally.
As such, composite projects will always produce .tsbuildinfo files.
outFile
当使用 outFile 时,构建信息文件的名称将基于输出文件的名称。例如,如果我们的输出 JavaScript 文件是 ./output/foo.js,那么在 incremental 标志下,TypeScript 将生成文件 ./output/foo.tsbuildinfo。如上所述,这可以通过 tsBuildInfoFile 选项进行控制。
🌐 When outFile is used, the build information file’s name will be based on the output file’s name.
As an example, if our output JavaScript file is ./output/foo.js, then under the incremental flag, TypeScript will generate the file ./output/foo.tsbuildinfo.
As above, this can be controlled with the tsBuildInfoFile option.
泛型函数的高阶类型推断
🌐 Higher order type inference from generic functions
TypeScript 3.4 现在可以在从其他泛型函数推断得到自由类型变量时生成泛型函数类型。这意味着许多函数组合模式在 3.4 中现在能更好地工作。
🌐 TypeScript 3.4 can now produce generic function types when inference from other generic functions produces free type variables for inferences. This means many function composition patterns now work better in 3.4.
为了更具体一些,让我们建立一些动机,并考虑以下 compose 函数:
🌐 To get more specific, let’s build up some motivation and consider the following compose function:
tsfunction compose<A, B, C>(f: (arg: A) => B, g: (arg: B) => C): (arg: A) => C {return (x) => g(f(x));}
compose 使用另外两个函数:
f接收一个类型为A的参数,并返回类型为B的值g接受一个类型为B的参数(返回类型为f),并返回一个类型为C的值
compose 然后返回一个函数,该函数将其参数先通过 f,然后通过 g。
调用此函数时,TypeScript 会尝试通过一种称为类型参数推断(type argument inference)的过程来确定 A、B 和 C 的类型。这个推断过程通常效果很好:
🌐 When calling this function, TypeScript will try to figure out the types of A, B, and C through a process called type argument inference.
This inference process usually works pretty well:
tsinterface Person {name: string;age: number;}function getDisplayName(p: Person) {return p.name.toLowerCase();}function getLength(s: string) {return s.length;}// has type '(p: Person) => number'const getDisplayNameLength = compose(getDisplayName, getLength);// works and returns the type 'number'getDisplayNameLength({ name: "Person McPersonface", age: 42 });
这里的推断过程相当简单,因为 getDisplayName 和 getLength 使用的类型可以很容易地引用。然而,在 TypeScript 3.3 及更早版本中,当将其他泛型函数传递给像 compose 这样的泛型函数时,效果并不理想。
🌐 The inference process is fairly straightforward here because getDisplayName and getLength use types that can easily be referenced.
However, in TypeScript 3.3 and earlier, generic functions like compose didn’t work so well when passed other generic functions.
tsinterface Box<T> {value: T;}function makeArray<T>(x: T): T[] {return [x];}function makeBox<U>(value: U): Box<U> {return { value };}// has type '(arg: {}) => Box<{}[]>'const makeBoxedArray = compose(makeArray, makeBox);makeBoxedArray("hello!").value[0].toUpperCase();// ~~~~~~~~~~~// error: Property 'toUpperCase' does not exist on type '{}'.
在较旧的版本中,TypeScript 会在从其他类型变量(如 T 和 U)推断时,推断空对象类型({})。
🌐 In older versions, TypeScript would infer the empty object type ({}) when inferring from other type variables like T and U.
在 TypeScript 3.4 中,在进行类型参数推断时,对于返回函数类型的泛型函数调用,TypeScript 会在适当情况下将泛型函数参数的类型参数传播到结果的函数类型上。
🌐 During type argument inference in TypeScript 3.4, for a call to a generic function that returns a function type, TypeScript will, as appropriate, propagate type parameters from generic function arguments onto the resulting function type.
换句话说,它不会生成类型,
🌐 In other words, instead of producing the type
ts(arg: {}) => Box<{}[]>
TypeScript 3.4 生成的类型
🌐 TypeScript 3.4 produces the type
ts<T>(arg: T) => Box<T[]>
注意到 T 已经从 makeArray 传播到了结果类型的类型参数列表中。这意味着 compose 参数的泛型特性得到了保留,并且我们的 makeBoxedArray 示例将可以正常使用!
🌐 Notice that T has been propagated from makeArray into the resulting type’s type parameter list.
This means that genericity from compose’s arguments has been preserved and our makeBoxedArray sample will just work!
tsinterface Box<T> {value: T;}function makeArray<T>(x: T): T[] {return [x];}function makeBox<U>(value: U): Box<U> {return { value };}// has type '<T>(arg: T) => Box<T[]>'const makeBoxedArray = compose(makeArray, makeBox);// works with no problem!makeBoxedArray("hello!").value[0].toUpperCase();
欲了解更多详情,你可以在原始变更处阅读更多。
🌐 For more details, you can read more at the original change.
ReadonlyArray 和 readonly 元组的改进
🌐 Improvements for ReadonlyArray and readonly tuples
TypeScript 3.4 使使用只读类数组类型变得更加容易。
🌐 TypeScript 3.4 makes it a little bit easier to use read-only array-like types.
ReadonlyArray的新语法
🌐 A new syntax for ReadonlyArray
ReadonlyArray 类型描述了只能读取的 Array。任何对 ReadonlyArray 的引用变量都不能添加、删除或替换数组中的任何元素。
🌐 The ReadonlyArray type describes Arrays that can only be read from.
Any variable with a reference to a ReadonlyArray can’t add, remove, or replace any elements of the array.
tsfunction foo(arr: ReadonlyArray<string>) {arr.slice(); // okayarr.push("hello!"); // error!}
虽然在不打算进行修改时使用 ReadonlyArray 比 Array 更好,但由于数组的语法更简洁,这通常很麻烦。具体来说,number[] 是 Array<number> 的简写,就像 Date[] 是 Array<Date> 的简写一样。
🌐 While it’s good practice to use ReadonlyArray over Array when no mutation is intended, it’s often been a pain given that arrays have a nicer syntax.
Specifically, number[] is a shorthand version of Array<number>, just as Date[] is a shorthand for Array<Date>.
TypeScript 3.4 引入了一种用于 ReadonlyArray 的新语法,该语法使用数组类型的新 readonly 修饰符。
🌐 TypeScript 3.4 introduces a new syntax for ReadonlyArray using a new readonly modifier for array types.
tsfunction foo(arr: readonly string[]) {arr.slice(); // okayarr.push("hello!"); // error!}
readonly 元组
🌐 readonly tuples
TypeScript 3.4 还引入了对 readonly 元组的新支持。我们可以在任何元组类型前加上 readonly 关键字,使其成为 readonly 元组,就像我们现在可以使用数组简写语法一样。如你所料,与普通元组的元素可以被写入不同,readonly 元组只允许从这些位置读取。
🌐 TypeScript 3.4 also introduces new support for readonly tuples.
We can prefix any tuple type with the readonly keyword to make it a readonly tuple, much like we now can with array shorthand syntax.
As you might expect, unlike ordinary tuples whose slots could be written to, readonly tuples only permit reading from those positions.
tsfunction foo(pair: readonly [string, string]) {console.log(pair[0]); // okaypair[1] = "hello!"; // error}
普通元组是从 Array 扩展的类型——一个包含类型为 T1、T2、… Tn 元素的元组从 Array< T1 | T2 | … Tn > 扩展而来——readonly 元组是从 ReadonlyArray 扩展的类型。因此,一个包含 T1、T2、… Tn 元素的 readonly 元组从 ReadonlyArray< T1 | T2 | … Tn 扩展而来。
readonly 映射类型修饰符和 readonly 数组
🌐 readonly mapped type modifiers and readonly arrays
在早期版本的 TypeScript 中,我们将映射类型泛化,使其可以在类数组类型上以不同的方式操作。这意味着像 Boxify 这样的映射类型可以同时作用于数组和元组。
🌐 In earlier versions of TypeScript, we generalized mapped types to operate differently on array-like types.
This meant that a mapped type like Boxify could work on arrays and tuples alike.
tsinterface Box<T> {value: T;}type Boxify<T> = {[K in keyof T]: Box<T[K]>;};// { a: Box<string>, b: Box<number> }type A = Boxify<{ a: string; b: number }>;// Array<Box<number>>type B = Boxify<number[]>;// [Box<string>, Box<number>]type C = Boxify<[string, boolean]>;
不幸的是,像 Readonly 工具类型这样的映射类型在数组和元组类型上实际上不起作用。
🌐 Unfortunately, mapped types like the Readonly utility type were effectively no-ops on array and tuple types.
ts// lib.d.tstype Readonly<T> = {readonly [K in keyof T]: T[K];};// How code acted *before* TypeScript 3.4// { readonly a: string, readonly b: number }type A = Readonly<{ a: string; b: number }>;// number[]type B = Readonly<number[]>;// [string, boolean]type C = Readonly<[string, boolean]>;
在 TypeScript 3.4 中,映射类型中的 readonly 修饰符会自动将类数组类型转换为其对应的 readonly 类型。
🌐 In TypeScript 3.4, the readonly modifier in a mapped type will automatically convert array-like types to their corresponding readonly counterparts.
ts// How code acts now *with* TypeScript 3.4// { readonly a: string, readonly b: number }type A = Readonly<{ a: string; b: number }>;// readonly number[]type B = Readonly<number[]>;// readonly [string, boolean]type C = Readonly<[string, boolean]>;
同样,你可以编写一个像 Writable 的实用类型映射类型,它可以去除 readonly 的特性,从而将 readonly 数组容器转换回它们的可变等价物。
🌐 Similarly, you could write a utility type like Writable mapped type that strips away readonly-ness, and that would convert readonly array containers back to their mutable equivalents.
tstype Writable<T> = {-readonly [K in keyof T]: T[K];};// { a: string, b: number }type A = Writable<{readonly a: string;readonly b: number;}>;// number[]type B = Writable<readonly number[]>;// [string, boolean]type C = Writable<readonly [string, boolean]>;
警告
🌐 Caveats
尽管如此,readonly 类型修饰符只能用于数组类型和元组类型的语法。它不是通用的类型操作符。
🌐 Despite its appearance, the readonly type modifier can only be used for syntax on array types and tuple types.
It is not a general-purpose type operator.
tslet err1: readonly Set<number>; // error!let err2: readonly Array<boolean>; // error!let okay: readonly boolean[]; // works fine
你可以在拉取请求中查看更多详情。
🌐 You can see more details in the pull request.
const 断言
🌐 const assertions
TypeScript 3.4 引入了一种用于字面值的新构造,称为 const 断言。其语法是用 const 代替类型名称的类型断言(例如 123 as const)。当我们使用 const 断言构造新的字面表达式时,我们可以向语言表明
🌐 TypeScript 3.4 introduces a new construct for literal values called const assertions.
Its syntax is a type assertion with const in place of the type name (e.g. 123 as const).
When we construct new literal expressions with const assertions, we can signal to the language that
- 该表达式中的字面量类型不应被扩展(例如,不应从
"hello"变为string) - 对象字面量获取
readonly属性 - 数组字面量变为
readonly元组
ts// Type '"hello"'let x = "hello" as const;// Type 'readonly [10, 20]'let y = [10, 20] as const;// Type '{ readonly text: "hello" }'let z = { text: "hello" } as const;
在 .tsx 文件之外,也可以使用尖括号断言语法。
🌐 Outside of .tsx files, the angle bracket assertion syntax can also be used.
ts// Type '"hello"'let x = <const>"hello";// Type 'readonly [10, 20]'let y = <const>[10, 20];// Type '{ readonly text: "hello" }'let z = <const>{ text: "hello" };
此功能意味着通常可以省略那些原本只是为了向编译器提示不可变性的类型。
🌐 This feature means that types that would otherwise be used just to hint immutability to the compiler can often be omitted.
ts// Works with no types referenced or declared.// We only needed a single const assertion.function getShapes() {let result = [{ kind: "circle", radius: 100 },{ kind: "square", sideLength: 50 },] as const;return result;}for (const shape of getShapes()) {// Narrows perfectly!if (shape.kind === "circle") {console.log("Circle radius", shape.radius);} else {console.log("Square side length", shape.sideLength);}}
注意,上述内容不需要类型注解。
const 断言允许 TypeScript 获取表达式的最具体类型。
🌐 Notice the above needed no type annotations.
The const assertion allowed TypeScript to take the most specific type of the expression.
如果你选择不使用 TypeScript 的 enum 构造,这甚至可以用于在普通 JavaScript 代码中启用类似 enum 的模式。
🌐 This can even be used to enable enum-like patterns in plain JavaScript code if you choose not to use TypeScript’s enum construct.
tsexport const Colors = {red: "RED",blue: "BLUE",green: "GREEN",} as const;// or use an 'export default'export default {red: "RED",blue: "BLUE",green: "GREEN",} as const;
警告
🌐 Caveats
需要注意的一点是,const 断言只能立即应用于简单的字面量表达式。
🌐 One thing to note is that const assertions can only be applied immediately on simple literal expressions.
ts// Error! A 'const' assertion can only be applied// to a string, number, boolean, array, or object literal.let a = (Math.random() < 0.5 ? 0 : 1) as const;let b = (60 * 60 * 1000) as const;// Works!let c = Math.random() < 0.5 ? (0 as const) : (1 as const);let d = 3_600_000 as const;
另一件需要记住的事情是,const 上下文不会立即将表达式转换为完全不可变。
🌐 Another thing to keep in mind is that const contexts don’t immediately convert an expression to be fully immutable.
tslet arr = [1, 2, 3, 4];let foo = {name: "foo",contents: arr,} as const;foo.name = "bar"; // error!foo.contents = []; // error!foo.contents.push(5); // ...works!
欲了解更多详情,你可以查看相应的拉取请求。
🌐 For more details, you can check out the respective pull request.
globalThis 的类型检查
🌐 Type-checking for globalThis
TypeScript 3.4 引入了对 ECMAScript 新的 globalThis 的类型检查支持——这是一个全局变量,它指向全局作用域。与上述解决方案不同,globalThis 提供了一种标准方式来访问全局作用域,可以在不同环境中使用。
🌐 TypeScript 3.4 introduces support for type-checking ECMAScript’s new globalThis - a global variable that, well, refers to the global scope.
Unlike the above solutions, globalThis provides a standard way for accessing the global scope which can be used across different environments.
ts// in a global file:var abc = 100;// Refers to 'abc' from above.globalThis.abc = 200;
请注意,用 let 和 const 声明的全局变量不会显示在 globalThis 上。
🌐 Note that global variables declared with let and const don’t show up on globalThis.
tslet answer = 42;// error! Property 'answer' does not exist on 'typeof globalThis'.globalThis.answer = 333333;
同样重要的是要注意,TypeScript 在编译到较旧版本的 ECMAScript 时不会转换对 globalThis 的引用。因此,除非你面向的是已经支持 globalThis 的最新浏览器,否则你可能需要使用合适的 polyfill。
🌐 It’s also important to note that TypeScript doesn’t transform references to globalThis when compiling to older versions of ECMAScript.
As such, unless you’re targeting evergreen browsers (which already support globalThis), you may want to use an appropriate polyfill instead.
有关实现的更多详细信息,请参见该功能的拉取请求。
🌐 For more details on the implementation, see the feature’s pull request.