TypeScript 3.0

项目引用

🌐 Project References

TypeScript 3.0 引入了一个新的概念——项目引用。项目引用允许 TypeScript 项目依赖其他 TypeScript 项目——具体来说,允许 tsconfig.json 文件引用其他 tsconfig.json 文件。指定这些依赖可以更容易地将代码拆分为更小的项目,因为它为 TypeScript(以及相关工具)提供了一种理解构建顺序和输出结构的方法。

🌐 TypeScript 3.0 introduces a new concept of project references. Project references allow TypeScript projects to depend on other TypeScript projects - specifically, allowing tsconfig.json files to reference other tsconfig.json files. Specifying these dependencies makes it easier to split your code into smaller projects, since it gives TypeScript (and tools around it) a way to understand build ordering and output structure.

TypeScript 3.0 还为 tsc 引入了一种新模式,即 --build 标志,它与项目引用协同工作,以实现更快的 TypeScript 构建。

🌐 TypeScript 3.0 also introduces a new mode for tsc, the --build flag, that works hand-in-hand with project references to enable faster TypeScript builds.

有关更多文档,请参见 项目参考手册页面

🌐 See Project References handbook page for more documentation.

剩余参数和扩展表达式中的元组

🌐 Tuples in rest parameters and spread expressions

TypeScript 3.0 增加了对将函数参数列表作为元组类型进行交互的多项新功能的支持。 TypeScript 3.0 增加了对以下内容的支持:

🌐 TypeScript 3.0 adds support to multiple new capabilities to interact with function parameter lists as tuple types. TypeScript 3.0 adds support for:

借助这些特性,我们可以将一些高阶函数强类型化,从而转换函数及其参数列表。

🌐 With these features it becomes possible to strongly type a number of higher-order functions that transform functions and their parameter lists.

元组类型的剩余参数

🌐 Rest parameters with tuple types

当一个剩余参数具有元组类型时,元组类型会被展开成一系列独立的参数。例如,以下两个声明是等效的:

🌐 When a rest parameter has a tuple type, the tuple type is expanded into a sequence of discrete parameters. For example the following two declarations are equivalent:

ts
declare function foo(...args: [number, string, boolean]): void;
ts
declare function foo(args_0: number, args_1: string, args_2: boolean): void;

元组类型的展开表达式

🌐 Spread expressions with tuple types

当函数调用包含元组类型的展开表达式作为最后一个参数时,该展开表达式对应于元组元素类型的离散参数序列。

🌐 When a function call includes a spread expression of a tuple type as the last argument, the spread expression corresponds to a sequence of discrete arguments of the tuple element types.

因此,以下调用是等效的:

🌐 Thus, the following calls are equivalent:

ts
const args: [number, string, boolean] = [42, "hello", true];
foo(42, "hello", true);
foo(args[0], args[1], args[2]);
foo(...args);

泛型剩余参数

🌐 Generic rest parameters

剩余参数可以使用受数组类型约束的泛型类型,并且类型推断可以为这种泛型剩余参数推断元组类型。这使得高阶捕获和展开部分参数列表成为可能:

🌐 A rest parameter is permitted to have a generic type that is constrained to an array type, and type inference can infer tuple types for such generic rest parameters. This enables higher-order capturing and spreading of partial parameter lists:

示例

🌐 Example

ts
declare function bind<T, U extends any[], V>(
f: (x: T, ...args: U) => V,
x: T
): (...args: U) => V;
declare function f3(x: number, y: string, z: boolean): void;
const f2 = bind(f3, 42); // (y: string, z: boolean) => void
const f1 = bind(f2, "hello"); // (z: boolean) => void
const f0 = bind(f1, true); // () => void
f3(42, "hello", true);
f2("hello", true);
f1(true);
f0();

在上述 f2 的声明中,类型推断会分别为 TUV 推断出类型 number[string, boolean]void

🌐 In the declaration of f2 above, type inference infers types number, [string, boolean] and void for T, U and V respectively.

请注意,当一个元组类型是从一系列参数推断出来并随后展开成参数列表时(如 U 的情况),在展开过程中会使用原始的参数名称(但是,这些名称没有语义意义,也无法以其他方式观察到)。

🌐 Note that when a tuple type is inferred from a sequence of parameters and later expanded into a parameter list, as is the case for U, the original parameter names are used in the expansion (however, the names have no semantic meaning and are not otherwise observable).

元组类型中的可选元素

🌐 Optional elements in tuple types

元组类型现在允许在元素类型上使用 ? 后缀来表示该元素是可选的:

🌐 Tuple types now permit a ? postfix on element types to indicate that the element is optional:

示例

🌐 Example

ts
let t: [number, string?, boolean?];
t = [42, "hello", true];
t = [42, "hello"];
t = [42];

strictNullChecks 模式下,? 修饰符会自动在元素类型中包含 undefined,类似于可选参数。

🌐 In strictNullChecks mode, a ? modifier automatically includes undefined in the element type, similar to optional parameters.

如果一个元组类型的元素在其类型上带有后缀 ? 修饰符,并且它右侧的所有元素也都有 ? 修饰符,则该元素可以被省略。

🌐 A tuple type permits an element to be omitted if it has a postfix ? modifier on its type and all elements to the right of it also have ? modifiers.

当推断剩余参数的元组类型时,源中的可选参数将成为推断类型中的可选元组元素。

🌐 When tuple types are inferred for rest parameters, optional parameters in the source become optional tuple elements in the inferred type.

具有可选元素的元组类型的 length 属性是表示可能长度的数字字面量类型的联合类型。例如,元组类型 [number, string?, boolean?]length 属性的类型是 1 | 2 | 3

🌐 The length property of a tuple type with optional elements is a union of numeric literal types representing the possible lengths. For example, the type of the length property in the tuple type [number, string?, boolean?] is 1 | 2 | 3.

元组类型中的剩余元素

🌐 Rest elements in tuple types

元组类型的最后一个元素可以是形式为 ...X 的剩余元素,其中 X 是数组类型。 剩余元素表示元组类型是开放的,可以有零个或多个额外的数组元素类型。 例如,[number, ...string[]] 表示具有一个 number 元素,后跟任意数量 string 元素的元组。

🌐 The last element of a tuple type can be a rest element of the form ...X, where X is an array type. A rest element indicates that the tuple type is open-ended and may have zero or more additional elements of the array element type. For example, [number, ...string[]] means tuples with a number element followed by any number of string elements.

示例

🌐 Example

ts
function tuple<T extends any[]>(...args: T): T {
return args;
}
const numbers: number[] = getArrayOfNumbers();
const t1 = tuple("foo", 1, true); // [string, number, boolean]
const t2 = tuple("bar", ...numbers); // [string, ...number[]]

带有剩余元素的元组类型的 length 属性的类型是 number

🌐 The type of the length property of a tuple type with a rest element is number.

新的 unknown 顶层类型

🌐 New unknown top type

TypeScript 3.0 引入了一个新的顶层类型 unknownunknownany 的类型安全对应。 任何东西都可以赋值给 unknown,但是 unknown 除了自己和 any 外,不能赋值给其他任何类型,除非使用类型断言或基于控制流的缩小。 同样,未经断言或缩小到更具体的类型,不允许对 unknown 进行任何操作。

🌐 TypeScript 3.0 introduces a new top type unknown. unknown is the type-safe counterpart of any. Anything is assignable to unknown, but unknown isn’t assignable to anything but itself and any without a type assertion or a control flow based narrowing. Likewise, no operations are permitted on an unknown without first asserting or narrowing to a more specific type.

示例

🌐 Example

ts
// In an intersection everything absorbs unknown
type T00 = unknown & null; // null
type T01 = unknown & undefined; // undefined
type T02 = unknown & null & undefined; // null & undefined (which becomes never)
type T03 = unknown & string; // string
type T04 = unknown & string[]; // string[]
type T05 = unknown & unknown; // unknown
type T06 = unknown & any; // any
// In a union an unknown absorbs everything
type T10 = unknown | null; // unknown
type T11 = unknown | undefined; // unknown
type T12 = unknown | null | undefined; // unknown
type T13 = unknown | string; // unknown
type T14 = unknown | string[]; // unknown
type T15 = unknown | unknown; // unknown
type T16 = unknown | any; // any
// Type variable and unknown in union and intersection
type T20<T> = T & {}; // T & {}
type T21<T> = T | {}; // T | {}
type T22<T> = T & unknown; // T
type T23<T> = T | unknown; // unknown
// unknown in conditional types
type T30<T> = unknown extends T ? true : false; // Deferred
type T31<T> = T extends unknown ? true : false; // Deferred (so it distributes)
type T32<T> = never extends T ? true : false; // true
type T33<T> = T extends never ? true : false; // Deferred
// keyof unknown
type T40 = keyof any; // string | number | symbol
type T41 = keyof unknown; // never
// Only equality operators are allowed with unknown
function f10(x: unknown) {
x == 5;
x !== 10;
x >= 0; // Error
x + 1; // Error
x * 2; // Error
-x; // Error
+x; // Error
}
// No property accesses, element accesses, or function calls
function f11(x: unknown) {
x.foo; // Error
x[5]; // Error
x(); // Error
new x(); // Error
}
// typeof, instanceof, and user defined type predicates
declare function isFunction(x: unknown): x is Function;
function f20(x: unknown) {
if (typeof x === "string" || typeof x === "number") {
x; // string | number
}
if (x instanceof Error) {
x; // Error
}
if (isFunction(x)) {
x; // Function
}
}
// Homomorphic mapped type over unknown
type T50<T> = { [P in keyof T]: number };
type T51 = T50<any>; // { [x: string]: number }
type T52 = T50<unknown>; // {}
// Anything is assignable to unknown
function f21<T>(pAny: any, pNever: never, pT: T) {
let x: unknown;
x = 123;
x = "hello";
x = [1, 2, 3];
x = new Error();
x = x;
x = pAny;
x = pNever;
x = pT;
}
// unknown assignable only to itself and any
function f22(x: unknown) {
let v1: any = x;
let v2: unknown = x;
let v3: object = x; // Error
let v4: string = x; // Error
let v5: string[] = x; // Error
let v6: {} = x; // Error
let v7: {} | null | undefined = x; // Error
}
// Type parameter 'T extends unknown' not related to object
function f23<T extends unknown>(x: T) {
let y: object = x; // Error
}
// Anything but primitive assignable to { [x: string]: unknown }
function f24(x: { [x: string]: unknown }) {
x = {};
x = { a: 5 };
x = [1, 2, 3];
x = 123; // Error
}
// Locals of type unknown always considered initialized
function f25() {
let x: unknown;
let y = x;
}
// Spread of unknown causes result to be unknown
function f26(x: {}, y: unknown, z: any) {
let o1 = { a: 42, ...x }; // { a: number }
let o2 = { a: 42, ...x, ...y }; // unknown
let o3 = { a: 42, ...x, ...y, ...z }; // any
}
// Functions with unknown return type don't need return expressions
function f27(): unknown {}
// Rest type cannot be created from unknown
function f28(x: unknown) {
let { ...a } = x; // Error
}
// Class properties of type unknown don't need definite assignment
class C1 {
a: string; // Error
b: unknown;
c: any;
}

在 JSX 中支持 defaultProps

🌐 Support for defaultProps in JSX

TypeScript 2.9 及更早版本没有在 JSX 组件中利用 React defaultProps 声明。用户经常需要将属性声明为可选,并在 render 内使用非空断言,或者他们会在导出组件之前使用类型断言来修复组件的类型。

🌐 TypeScript 2.9 and earlier didn’t leverage React defaultProps declarations inside JSX components. Users would often have to declare properties optional and use non-null assertions inside of render, or they’d use type-assertions to fix up the type of the component before exporting it.

TypeScript 3.0 在 JSX 命名空间中新增了一个名为 LibraryManagedAttributes 的类型别名。这个辅助类型定义了组件 Props 类型的转换,在使用它检查针对该组件的 JSX 表达式之前;从而允许自定义,例如:如何处理提供的属性与推断属性之间的冲突、如何映射推断、如何处理可选性,以及如何组合来自不同来源的推断。

🌐 TypeScript 3.0 adds support for a new type alias in the JSX namespace called LibraryManagedAttributes. This helper type defines a transformation on the component’s Props type, before using to check a JSX expression targeting it; thus allowing customization like: how conflicts between provided props and inferred props are handled, how inferences are mapped, how optionality is handled, and how inferences from differing places should be combined.

简而言之,使用这种通用类型,我们可以为诸如 defaultProps 之类的东西建模 React 的特定行为,并在某种程度上支持 propTypes

🌐 In short using this general type, we can model React’s specific behavior for things like defaultProps and, to some extent, propTypes.

tsx
export interface Props {
name: string;
}
export class Greet extends React.Component<Props> {
render() {
const { name } = this.props;
return <div>Hello {name.toUpperCase()}!</div>;
}
static defaultProps = { name: "world" };
}
// Type-checks! No type assertions needed!
let el = <Greet />;

警告

🌐 Caveats

defaultProps 的显式类型

🌐 Explicit types on defaultProps

默认属性是从 defaultProps 属性类型推断的。如果添加了显式类型注解,例如 static defaultProps: Partial<Props>;,编译器将无法识别哪些属性具有默认值(因为 defaultProps 的类型包括 Props 的所有属性)。

可以使用 static defaultProps: Pick<Props, "name">; 作为显式类型注解,或者像上面的示例那样不添加类型注解。

对于函数组件(以前称为 SFC),请使用 ES2015 默认初始化器:

🌐 For function components (formerly known as SFCs) use ES2015 default initializers:

tsx
function Greet({ name = "world" }: Props) {
return <div>Hello {name.toUpperCase()}!</div>;
}

@types/React 的更改

🌐 Changes to @types/React

仍然需要对应的更改以将 LibraryManagedAttributes 定义添加到 @types/React 中的 JSX 命名空间。请记住,存在一些限制。

🌐 Corresponding changes to add LibraryManagedAttributes definition to the JSX namespace in @types/React are still needed. Keep in mind that there are some limitations.

/// <reference lib="..." /> 参考指令

🌐 /// <reference lib="..." /> reference directives

TypeScript 添加了一个新的三斜杠引用指令(/// <reference lib="name" />),允许文件显式包含现有的内置 lib 文件。

🌐 TypeScript adds a new triple-slash-reference directive (/// <reference lib="name" />), allowing a file to explicitly include an existing built-in lib file.

内置 lib 文件的引用方式与 tsconfig.json 中的 lib 编译器选项相同(例如使用 lib="es2015" 而不是 lib="lib.es2015.d.ts",等等)。

🌐 Built-in lib files are referenced in the same fashion as the lib compiler option in tsconfig.json (e.g. use lib="es2015" and not lib="lib.es2015.d.ts", etc.).

对于依赖内置类型的声明文件作者,例如 DOM API 或内置 JS 运行时构造函数如 SymbolIterable,建议使用三斜杠引用 lib 指令。以前,这些 .d.ts 文件必须添加这些类型的前向/重复声明。

🌐 For declaration file authors who rely on built-in types, e.g. DOM APIs or built-in JS run-time constructors like Symbol or Iterable, triple-slash-reference lib directives are recommended. Previously these .d.ts files had to add forward/duplicate declarations of such types.

示例

🌐 Example

在编译中将 /// <reference lib="es2017.string" /> 用于其中一个文件,相当于使用 --lib es2017.string 进行编译。

🌐 Using /// <reference lib="es2017.string" /> to one of the files in a compilation is equivalent to compiling with --lib es2017.string.

ts
/// <reference lib="es2017.string" />
"foo".padStart(4);