TypeScript 3.4

使用 --incremental 标志实现更快的后续构建

TypeScript 3.4 引入了一个名为 incremental 的新标志,它告诉 TypeScript 保存上次编译时的项目图信息。下次使用 incremental 调用 TypeScript 时,它将利用这些信息以成本最低的方式对项目中的更改进行类型检查和输出。

// tsconfig.json
{
"": true,
"": "./lib"
},
"": ["./src"]
}

在默认设置下,当我们运行 tsc 时,TypeScript 会在输出目录 (./lib) 中寻找名为 .tsbuildinfo 的文件。如果 ./lib/.tsbuildinfo 不存在,它将被生成。但如果它存在,tsc 将尝试使用该文件进行增量类型检查并更新输出文件。

这些 .tsbuildinfo 文件可以安全删除,并且对我们运行时的代码没有任何影响——它们纯粹用于加快编译速度。我们还可以通过 tsBuildInfoFile 选项为它们随意命名,并放置在任何我们想要的位置。

// front-end.tsconfig.json
{
"": true,
"": "./buildcache/front-end",
"": "./lib"
},
"": ["./src"]
}

复合项目 (Composite projects)

复合项目(将 composite 设置为 truetsconfig.json)的部分意图是不同项目之间的引用可以增量构建。因此,复合项目将始终生成 .tsbuildinfo 文件。

outFile

当使用 outFile 时,构建信息文件的名称将基于输出文件的名称。例如,如果我们的输出 JavaScript 文件是 ./output/foo.js,那么在使用 incremental 标志时,TypeScript 将生成 ./output/foo.tsbuildinfo 文件。如上所述,这可以通过 tsBuildInfoFile 选项进行控制。

泛型函数的高阶类型推断

TypeScript 3.4 现在可以产生泛型函数类型,即使从其他泛型函数推断产生自由类型变量时也是如此。这意味着许多函数组合模式在 3.4 中能够更好地工作。

具体来说,让我们建立一些动力并考虑以下 compose 函数:

ts
function 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,它接收一个类型为 Bf 返回的类型)的参数,并返回一个类型为 C 的值

然后 compose 返回一个函数,该函数将其参数通过 f 处理,然后通过 g 处理。

在调用此函数时,TypeScript 将尝试通过一个称为类型参数推断的过程找出 ABC 的类型。此推断过程通常非常有效。

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 这样的泛型函数时,效果并不理想。

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 '{}'.

在旧版本中,当从其他类型变量(如 TU)进行推断时,TypeScript 会推断为空对象类型 ({})。

在 TypeScript 3.4 的类型参数推断过程中,对于调用返回函数类型的泛型函数,TypeScript 根据需要将类型参数从泛型函数参数传播到结果函数类型上。

换句话说,TypeScript 3.4 不再产生该类型,而是产生

ts
(arg: {}) => Box<{}[]>

TypeScript 3.4 产生的类型

ts
<T>(arg: T) => Box<T[]>

请注意,T 已经从 makeArray 传播到了结果类型的类型参数列表中。这意味着 compose 参数的泛型特性得以保留,我们的 makeBoxedArray 示例将能够直接运行!

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();

有关更多详细信息,您可以阅读原始变更详情

ReadonlyArrayreadonly 元组的改进

TypeScript 3.4 使只读数组类类型的使用变得更加容易。

ReadonlyArray 的新语法

ReadonlyArray 类型描述了只能从中读取的 Array。任何引用 ReadonlyArray 的变量都无法添加、删除或替换数组中的任何元素。

ts
function foo(arr: ReadonlyArray<string>) {
arr.slice(); // okay
arr.push("hello!"); // error!
}

虽然在不打算进行突变时使用 ReadonlyArray 代替 Array 是很好的做法,但由于数组具有更友好的语法,这通常很麻烦。具体来说,number[]Array<number> 的简写,就像 Date[]Array<Date> 的简写一样。

TypeScript 3.4 为 ReadonlyArray 引入了一种新语法,使用新的 readonly 修饰符用于数组类型。

ts
function foo(arr: readonly string[]) {
arr.slice(); // okay
arr.push("hello!"); // error!
}

readonly 元组

TypeScript 3.4 还引入了对 readonly 元组的新支持。我们可以在任何元组类型前加上 readonly 关键字,使其成为 readonly 元组,就像我们现在使用数组简写语法一样。正如您所料,与可以写入插槽的普通元组不同,readonly 元组仅允许从这些位置读取。

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

与普通元组是扩展自 Array 的类型一样——元素类型为 T1, T2, … Tn 的元组扩展自 Array< T1 | T2 | … Tn >——readonly 元组是扩展自 ReadonlyArray 的类型。因此,元素为 T1, T2, … Tnreadonly 元组扩展自 ReadonlyArray< T1 | T2 | … Tn >

readonly 映射类型修饰符和 readonly 数组

在较早版本的 TypeScript 中,我们泛化了映射类型以在数组类类型上进行不同的操作。这意味着像 Boxify 这样的映射类型可以同时作用于数组和元组。

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 工具类型这样的映射类型对于数组和元组类型实际上是无操作的 (no-ops)。

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 对等物。

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 数组容器转换回其可变的等价物。

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]>;

注意事项

尽管看起来如此,readonly 类型修饰符只能用于数组类型和元组类型的语法中。它不是通用的类型运算符。

ts
let err1: readonly Set<number>; // error!
let err2: readonly Array<boolean>; // error!
let okay: readonly boolean[]; // works fine

您可以在拉取请求中查看更多详细信息

const 断言

TypeScript 3.4 为字面量值引入了一种称为 const 断言的新结构。其语法是类型断言,其中 const 代替了类型名称(例如 123 as const)。当我们使用 const 断言构造新的字面量表达式时,我们可以向语言发出信号,即:

  • 该表达式中的任何字面量类型都不应被扩大(例如,不会从 "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 文件之外,也可以使用尖括号断言语法。

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" };

这一功能意味着,那些原本仅用于向编译器暗示不可变性的类型往往可以省略。

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 获取表达式的最具体类型。

如果您选择不使用 TypeScript 的 enum 结构,这甚至可以用于在纯 JavaScript 代码中启用类似 enum 的模式。

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;

注意事项

需要注意的一点是,const 断言只能立即应用于简单的字面量表达式。

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 上下文不会立即将表达式转换为完全不可变的。

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!

有关更多详细信息,您可以查看相关的拉取请求

globalThis 的类型检查

TypeScript 3.4 引入了对 ECMAScript 新增的 globalThis 的类型检查支持——这是一个全局变量,引用全局作用域。与上述解决方案不同,globalThis 提供了一种访问全局作用域的标准方式,可以在不同环境中使用。

ts
// in a global file:
var abc = 100;
// Refers to 'abc' from above.
globalThis.abc = 200;

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

ts
let answer = 42;
// error! Property 'answer' does not exist on 'typeof globalThis'.
globalThis.answer = 333333;

还需要注意的是,TypeScript 在编译为旧版本 ECMAScript 时,不会转换对 globalThis 的引用。因此,除非您针对的是 evergreen 浏览器(已经支持 globalThis),否则您可能需要使用适当的 polyfill

有关实现的更多详细信息,请参阅该功能的拉取请求

TypeScript 文档是一个开源项目。请通过发送 Pull Request 帮助我们改进这些页面 ❤

此页面的贡献者
DRDaniel Rosenwasser (51)
OTOrta Therox (14)
SShun 🎴 (1)
JBJack Bates (1)
MUMasato Urai (1)
4+

最后更新:2026 年 3 月 27 日