TypeScript 3.4

使用 --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.jsoncomposite 设置为 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:

ts
function compose<A, B, C>(f: (arg: A) => B, g: (arg: B) => C): (arg: A) => C {
return (x) => g(f(x));
}

compose 接受另外两个函数:

¥compose takes two other functions:

  • f 接受一些(类型为 A)的参数,并返回一个 B 类型的值

    ¥f which takes some argument (of type A) and returns a value of type B

  • g 接受一个 B 类型(返回的是 f 类型)的参数,并返回一个 C 类型的值

    ¥g which takes an argument of type B (the type f returned), and returns a value of type C

compose 返回一个函数,该函数将其参数传递给 f,然后传递给 g

¥compose then returns a function which feeds its argument through f and then g.

调用此函数时,TypeScript 将尝试通过称为类型参数推断的过程来确定 ABC 的类型。这个推断过程通常效果很好:

¥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:

ts
interface 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 });

这里的推断过程相当简单,因为 getDisplayNamegetLength 使用的类型易于引用。但是,在 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.

ts
interface 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 在从其他类型变量(例如 TU)推断时,会推断出空对象类型 ({})。

¥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!

ts
interface 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.

改进了 ReadonlyArrayreadonly 元组

¥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.

ts
function foo(arr: ReadonlyArray<string>) {
arr.slice(); // okay
arr.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.

ts
function foo(arr: readonly string[]) {
arr.slice(); // okay
arr.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.

ts
function foo(pair: readonly [string, string]) {
console.log(pair[0]); // okay
pair[1] = "hello!"; // error
}

与普通元组是从 Array 扩展的类型相同。 - 一个包含 T1T2 等类型元素的元组Tn 扩展自 Array< T1 | T2 | …Tn > - readonly 元组是从 ReadonlyArray 扩展而来的类型。readonly 元组包含元素 T1T2、…Tn 扩展自 ReadonlyArray< T1 | T2 | …Tn.

¥The same way that ordinary tuples are types that extend from Array - a tuple with elements of type T1, T2, … Tn extends from Array< T1 | T2 | … Tn > - readonly tuples are types that extend from ReadonlyArray. So a readonly tuple with elements T1, T2, … Tn extends from 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.

ts
interface 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.ts
type 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.

ts
type 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.

ts
let 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)。

    ¥no literal types in that expression should be widened (e.g. no going from "hello" to string)

  • 对象字面量获取 readonly 属性

    ¥object literals get readonly properties

  • 数组字面量变为 readonly 元组

    ¥array literals become readonly tuples

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.

ts
export 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
// 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.

ts
let 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;

请注意,使用 letconst 声明的全局变量不会显示在 globalThis 上。

¥Note that global variables declared with let and const don’t show up on globalThis.

ts
let 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.