条件类型
¥Conditional Types
TypeScript 2.8 引入了条件类型,它增加了表达非统一类型映射的能力。条件类型根据以类型关系测试表达的条件从两种可能的类型中选择一种:
¥TypeScript 2.8 introduces conditional types which add the ability to express non-uniform type mappings. A conditional type selects one of two possible types based on a condition expressed as a type relationship test:
ts
T extends U ? X : Y
上述类型意味着,当 T
可以赋值给 U
时,类型为 X
,否则为 Y
。
¥The type above means when T
is assignable to U
the type is X
, otherwise the type is Y
.
条件类型 T extends U ? X : Y
要么解析为 X
或 Y
,要么因为条件依赖于一个或多个类型变量而被推迟。解析还是延迟执行由以下方式确定:
¥A conditional type T extends U ? X : Y
is either resolved to X
or Y
, or deferred because the condition depends on one or more type variables.
Whether to resolve or defer is determined as follows:
-
首先,给定类型
T'
和U'
,它们是T
和U
的实例,其中所有出现的类型参数都被替换为any
,如果T'
不能分配给U'
,则条件类型将解析为Y
。直观地说,如果T
的最宽松实例化不能分配给U
的最宽松实例化,我们知道不会有任何实例化可以分配给U
,因此我们可以直接解析为Y
。¥First, given types
T'
andU'
that are instantiations ofT
andU
where all occurrences of type parameters are replaced withany
, ifT'
is not assignable toU'
, the conditional type is resolved toY
. Intuitively, if the most permissive instantiation ofT
is not assignable to the most permissive instantiation ofU
, we know that no instantiation will be and we can just resolve toY
. -
接下来,对于
U
中由infer
(稍后介绍)声明引入的每个类型变量,通过从T
推断到U
(使用与泛型函数类型推断相同的推断算法)来收集一组候选类型。对于给定的infer
类型变量V
,如果从共变位置推断出任何候选类型,则推断出的V
类型是这些候选类型的联合。否则,如果从逆变位置推断出任何候选类型,则推断出的V
类型是这些候选类型的交叉。否则,推断出的V
类型为never
。¥Next, for each type variable introduced by an
infer
(more later) declaration withinU
collect a set of candidate types by inferring fromT
toU
(using the same inference algorithm as type inference for generic functions). For a giveninfer
type variableV
, if any candidates were inferred from co-variant positions, the type inferred forV
is a union of those candidates. Otherwise, if any candidates were inferred from contra-variant positions, the type inferred forV
is an intersection of those candidates. Otherwise, the type inferred forV
isnever
. -
然后,给定一个类型
T''
,它是T
的实例,其中所有infer
类型变量都被替换为上一步推断出的类型,如果T''
肯定可以分配给U
,则条件类型将解析为X
。明确可赋值关系与常规可赋值关系相同,只是不考虑类型变量约束。直观地说,当一个类型肯定可以分配给另一个类型时,我们知道它可以分配给所有类型的实例化。¥Then, given a type
T''
that is an instantiation ofT
where allinfer
type variables are replaced with the types inferred in the previous step, ifT''
is definitely assignable toU
, the conditional type is resolved toX
. The definitely assignable relation is the same as the regular assignable relation, except that type variable constraints are not considered. Intuitively, when a type is definitely assignable to another type, we know that it will be assignable for all instantiations of those types. -
否则,条件依赖于一个或多个类型变量,并且条件类型将被延迟。
¥Otherwise, the condition depends on one or more type variables and the conditional type is deferred.
示例
¥Example
ts
type TypeName<T> = T extends string? "string": T extends number? "number": T extends boolean? "boolean": T extends undefined? "undefined": T extends Function? "function": "object";type T0 = TypeName<string>; // "string"type T1 = TypeName<"a">; // "string"type T2 = TypeName<true>; // "boolean"type T3 = TypeName<() => void>; // "function"type T4 = TypeName<string[]>; // "object"
分配条件类型
¥Distributive conditional types
被检查类型为裸类型参数的条件类型称为分布式条件类型。在实例化期间,分布式条件类型会自动分布到联合类型上。例如,如果 T extends U ? X : Y
的实例化带有 T
的类型参数 A | B | C
,则该实例将被解析为 (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
。
¥Conditional types in which the checked type is a naked type parameter are called distributive conditional types.
Distributive conditional types are automatically distributed over union types during instantiation.
For example, an instantiation of T extends U ? X : Y
with the type argument A | B | C
for T
is resolved as (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
.
示例
¥Example
ts
type T10 = TypeName<string | (() => void)>; // "string" | "function"type T12 = TypeName<string | string[] | undefined>; // "string" | "object" | "undefined"type T11 = TypeName<string[] | number[]>; // "object"
在分配条件类型 T extends U ? X : Y
的实例化中,条件类型中对 T
的引用将解析为联合类型的各个组成部分(即,在条件类型分配到联合类型之后,T
引用的是各个组成部分)。此外,在 X
中对 T
的引用具有额外的类型参数约束 U
(即,T
被视为可分配给 X
中的 U
)。
¥In instantiations of a distributive conditional type T extends U ? X : Y
, references to T
within the conditional type are resolved to individual constituents of the union type (i.e. T
refers to the individual constituents after the conditional type is distributed over the union type).
Furthermore, references to T
within X
have an additional type parameter constraint U
(i.e. T
is considered assignable to U
within X
).
示例
¥Example
ts
type BoxedValue<T> = { value: T };type BoxedArray<T> = { array: T[] };type Boxed<T> = T extends any[] ? BoxedArray<T[number]> : BoxedValue<T>;type T20 = Boxed<string>; // BoxedValue<string>;type T21 = Boxed<number[]>; // BoxedArray<number>;type T22 = Boxed<string | number[]>; // BoxedValue<string> | BoxedArray<number>;
请注意,T
在 Boxed<T>
的 true 分支中具有附加约束 any[]
,因此可以将数组的元素类型引用为 T[number]
。另外,请注意上例中条件类型是如何分布在联合类型上的。
¥Notice that T
has the additional constraint any[]
within the true branch of Boxed<T>
and it is therefore possible to refer to the element type of the array as T[number]
. Also, notice how the conditional type is distributed over the union type in the last example.
条件类型的分配属性可以方便地用于过滤联合类型:
¥The distributive property of conditional types can conveniently be used to filter union types:
ts
type Diff<T, U> = T extends U ? never : T; // Remove types from T that are assignable to Utype Filter<T, U> = T extends U ? T : never; // Remove types from T that are not assignable to Utype T30 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"type T31 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"type T32 = Diff<string | number | (() => void), Function>; // string | numbertype T33 = Filter<string | number | (() => void), Function>; // () => voidtype NonNullable<T> = Diff<T, null | undefined>; // Remove null and undefined from Ttype T34 = NonNullable<string | number | undefined>; // string | numbertype T35 = NonNullable<string | string[] | null | undefined>; // string | string[]function f1<T>(x: T, y: NonNullable<T>) {x = y; // Oky = x; // Error}function f2<T extends string | undefined>(x: T, y: NonNullable<T>) {x = y; // Oky = x; // Errorlet s1: string = x; // Errorlet s2: string = y; // Ok}
条件类型与映射类型结合使用时尤其有用:
¥Conditional types are particularly useful when combined with mapped types:
ts
type FunctionPropertyNames<T> = {[K in keyof T]: T[K] extends Function ? K : never;}[keyof T];type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;type NonFunctionPropertyNames<T> = {[K in keyof T]: T[K] extends Function ? never : K;}[keyof T];type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;interface Part {id: number;name: string;subparts: Part[];updatePart(newName: string): void;}type T40 = FunctionPropertyNames<Part>; // "updatePart"type T41 = NonFunctionPropertyNames<Part>; // "id" | "name" | "subparts"type T42 = FunctionProperties<Part>; // { updatePart(newName: string): void }type T43 = NonFunctionProperties<Part>; // { id: number, name: string, subparts: Part[] }
与并集和交叉类型类似,条件类型不允许递归引用自身。例如,以下代码是错误的。
¥Similar to union and intersection types, conditional types are not permitted to reference themselves recursively. For example the following is an error.
示例
¥Example
ts
type ElementType<T> = T extends any[] ? ElementType<T[number]> : T; // Error
条件类型中的类型推断
¥Type inference in conditional types
在条件类型的 extends
子句中,现在可以进行 infer
声明,从而引入要推断的类型变量。此类推断出的类型变量可以在条件类型的 true 分支中引用。同一类型变量可以有多个 infer
位置。
¥Within the extends
clause of a conditional type, it is now possible to have infer
declarations that introduce a type variable to be inferred.
Such inferred type variables may be referenced in the true branch of the conditional type.
It is possible to have multiple infer
locations for the same type variable.
例如,以下代码提取函数类型的返回类型:
¥For example, the following extracts the return type of a function type:
ts
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
条件类型可以嵌套,形成按顺序执行的模式匹配序列:
¥Conditional types can be nested to form a sequence of pattern matches that are evaluated in order:
ts
type Unpacked<T> = T extends (infer U)[]? U: T extends (...args: any[]) => infer U? U: T extends Promise<infer U>? U: T;type T0 = Unpacked<string>; // stringtype T1 = Unpacked<string[]>; // stringtype T2 = Unpacked<() => string>; // stringtype T3 = Unpacked<Promise<string>>; // stringtype T4 = Unpacked<Promise<string>[]>; // Promise<string>type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string
以下示例演示了如何在共变位置上为同一类型变量指定多个候选值,从而推断出联合类型:
¥The following example demonstrates how multiple candidates for the same type variable in co-variant positions causes a union type to be inferred:
ts
type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;type T10 = Foo<{ a: string; b: string }>; // stringtype T11 = Foo<{ a: string; b: number }>; // string | number
同样,如果同一类型变量在相反变量位置有多个候选,则会推断为交叉类型:
¥Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred:
ts
type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void }? U: never;type T20 = Bar<{ a: (x: string) => void; b: (x: string) => void }>; // stringtype T21 = Bar<{ a: (x: string) => void; b: (x: number) => void }>; // string & number
当从具有多个调用签名的类型(例如重载函数的类型)进行推断时,会根据最后一个签名进行推断(这可能是最宽松的包罗万象的情况)。无法根据参数类型列表执行重载决议。
¥When inferring from a type with multiple call signatures (such as the type of an overloaded function), inferences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not possible to perform overload resolution based on a list of argument types.
ts
declare function foo(x: string): number;declare function foo(x: number): string;declare function foo(x: string | number): string | number;type T30 = ReturnType<typeof foo>; // string | number
在常规类型参数的约束子句中不能使用 infer
声明:
¥It is not possible to use infer
declarations in constraint clauses for regular type parameters:
ts
type ReturnType<T extends (...args: any[]) => infer R> = R; // Error, not supported
但是,通过删除约束中的类型变量并改为指定条件类型,可以获得大致相同的效果:
¥However, much the same effect can be obtained by erasing the type variables in the constraint and instead specifying a conditional type:
ts
type AnyFunction = (...args: any[]) => any;type ReturnType<T extends AnyFunction> = T extends (...args: any[]) => infer R? R: any;
预定义条件类型
¥Predefined conditional types
TypeScript 2.8 为 lib.d.ts
添加了几个预定义的条件类型:
¥TypeScript 2.8 adds several predefined conditional types to lib.d.ts
:
-
Exclude<T, U>
— 从T
中排除可分配给U
的类型。¥
Exclude<T, U>
— Exclude fromT
those types that are assignable toU
. -
Extract<T, U>
— 从T
中提取可分配给U
的类型。¥
Extract<T, U>
— Extract fromT
those types that are assignable toU
. -
NonNullable<T>
— 从T
中排除null
和undefined
。¥
NonNullable<T>
— Excludenull
andundefined
fromT
. -
ReturnType<T>
— 获取函数类型的返回类型。¥
ReturnType<T>
— Obtain the return type of a function type. -
InstanceType<T>
— 获取构造函数类型的实例类型。¥
InstanceType<T>
— Obtain the instance type of a constructor function type.
示例
¥Example
ts
type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"type T02 = Exclude<string | number | (() => void), Function>; // string | numbertype T03 = Extract<string | number | (() => void), Function>; // () => voidtype T04 = NonNullable<string | number | undefined>; // string | numbertype T05 = NonNullable<(() => string) | string[] | null | undefined>; // (() => string) | string[]function f1(s: string) {return { a: 1, b: s };}class C {x = 0;y = 0;}type T10 = ReturnType<() => string>; // stringtype T11 = ReturnType<(s: string) => void>; // voidtype T12 = ReturnType<<T>() => T>; // {}type T13 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]type T14 = ReturnType<typeof f1>; // { a: number, b: string }type T15 = ReturnType<any>; // anytype T16 = ReturnType<never>; // anytype T17 = ReturnType<string>; // Errortype T18 = ReturnType<Function>; // Errortype T20 = InstanceType<typeof C>; // Ctype T21 = InstanceType<any>; // anytype T22 = InstanceType<never>; // anytype T23 = InstanceType<string>; // Errortype T24 = InstanceType<Function>; // Error
注意:
Exclude
类型是 此处 中建议的Diff
类型的正确实现。我们使用Exclude
这个名称是为了避免破坏定义Diff
的现有代码,而且我们认为这个名称能更好地传达类型的语义。我们没有包含Omit<T, K>
类型,因为它可以简单地写成Pick<T, Exclude<keyof T, K>>
。¥Note: The
Exclude
type is a proper implementation of theDiff
type suggested here. We’ve used the nameExclude
to avoid breaking existing code that defines aDiff
, plus we feel that name better conveys the semantics of the type. We did not include theOmit<T, K>
type because it is trivially written asPick<T, Exclude<keyof T, K>>
.
改进了对映射类型修饰符的控制
¥Improved control over mapped type modifiers
映射类型支持向映射属性添加 readonly
或 ?
修饰符,但不支持删除修饰符。这在 同态映射类型 中很重要,因为默认情况下会保留底层类型的修饰符。
¥Mapped types support adding a readonly
or ?
modifier to a mapped property, but they did not provide support for the ability to remove modifiers.
This matters in homomorphic mapped types which by default preserve the modifiers of the underlying type.
TypeScript 2.8 添加了映射类型添加或删除特定修饰符的功能。具体来说,映射类型中的 readonly
或 ?
属性修饰符现在可以以 +
或 -
作为前缀,以指示应添加或删除该修饰符。
¥TypeScript 2.8 adds the ability for a mapped type to either add or remove a particular modifier.
Specifically, a readonly
or ?
property modifier in a mapped type can now be prefixed with either +
or -
to indicate that the modifier should be added or removed.
示例
¥Example
ts
type MutableRequired<T> = { -readonly [P in keyof T]-?: T[P] }; // Remove readonly and ?type ReadonlyPartial<T> = { +readonly [P in keyof T]+?: T[P] }; // Add readonly and ?
不带 +
或 -
前缀的修饰符与带 +
前缀的修饰符相同。因此,上面的 ReadonlyPartial<T>
类型对应于
¥A modifier with no +
or -
prefix is the same as a modifier with a +
prefix. So, the ReadonlyPartial<T>
type above corresponds to
ts
type ReadonlyPartial<T> = { readonly [P in keyof T]?: T[P] }; // Add readonly and ?
使用此功能,lib.d.ts
现在有了一个新的 Required<T>
类型。此类型从 T
的所有属性中删除 ?
修饰符,从而使所有属性都成为必需属性。
¥Using this ability, lib.d.ts
now has a new Required<T>
type.
This type strips ?
modifiers from all properties of T
, thus making all properties required.
示例
¥Example
ts
type Required<T> = { [P in keyof T]-?: T[P] };
请注意,在 strictNullChecks
模式下,当同态映射类型从底层类型的属性中移除 ?
修饰符时,它也会从该属性的类型中移除 undefined
:
¥Note that in strictNullChecks
mode, when a homomorphic mapped type removes a ?
modifier from a property in the underlying type it also removes undefined
from the type of that property:
示例
¥Example
ts
type Foo = { a?: string }; // Same as { a?: string | undefined }type Bar = Required<Foo>; // Same as { a: string }
改进了交叉类型的 keyof
¥Improved keyof
with intersection types
在 TypeScript 2.8 中,应用于交叉类型的 keyof
会被转换为应用于每个交叉成分的 keyof
的并集。换句话说,keyof (A & B)
形式的类型将转换为 keyof A | keyof B
。此更改旨在解决与 keyof
表达式推断不一致的问题。
¥With TypeScript 2.8 keyof
applied to an intersection type is transformed to a union of keyof
applied to each intersection constituent.
In other words, types of the form keyof (A & B)
are transformed to be keyof A | keyof B
.
This change should address inconsistencies with inference from keyof
expressions.
示例
¥Example
ts
type A = { a: string };type B = { b: string };type T1 = keyof (A & B); // "a" | "b"type T2<T> = keyof (T & B); // keyof T | "b"type T3<U> = keyof (A & U); // "a" | keyof Utype T4<T, U> = keyof (T & U); // keyof T | keyof Utype T5 = T2<A>; // "a" | "b"type T6 = T3<B>; // "a" | "b"type T7 = T4<A, B>; // "a" | "b"
更好地处理 .js
文件中的命名空间模式
¥Better handling for namespace patterns in .js
files
TypeScript 2.8 添加了对理解 .js
文件中更多命名空间模式的支持。现在,顶层的空对象字面量声明(就像函数和类一样)在 JavaScript 中被识别为命名空间声明。
¥TypeScript 2.8 adds support for understanding more namespace patterns in .js
files.
Empty object literals declarations on top level, just like functions and classes, are now recognized as namespace declarations in JavaScript.
js
var ns = {}; // recognized as a declaration for a namespace `ns`ns.constant = 1; // recognized as a declaration for var `constant`
顶层赋值的行为也应相同;换句话说,var
或 const
声明不是必需的。
¥Assignments at the top-level should behave the same way; in other words, a var
or const
declaration is not required.
js
app = {}; // does NOT need to be `var app = {}`app.C = class {};app.f = function() {};app.prop = 1;
IIFE 作为命名空间声明
¥IIFEs as namespace declarations
返回函数、类或空对象字面量的 IIFE 也被识别为命名空间:
¥An IIFE returning a function, class or empty object literal, is also recognized as a namespace:
js
var C = (function() {function C(n) {this.p = n;}return C;})();C.staticProperty = 1;
默认声明
¥Defaulted declarations
“默认声明” 允许在逻辑或左侧引用声明名称的初始化器:
¥“Defaulted declarations” allow initializers that reference the declared name in the left side of a logical or:
js
my = window.my || {};my.app = my.app || {};
原型赋值
¥Prototype assignment
你可以将对象字面量直接赋值给原型属性。个人原型分配仍然有效:
¥You can assign an object literal directly to the prototype property. Individual prototype assignments still work too:
ts
var C = function(p) {this.p = p;};C.prototype = {m() {console.log(this.p);}};C.prototype.q = function(r) {return this.p === r;};
嵌套和合并声明
¥Nested and merged declarations
现在嵌套可以在任何级别进行,并且可以跨文件正确合并。之前两者都不是。
¥Nesting works to any level now, and merges correctly across files. Previously neither was the case.
js
var app = window.app || {};app.C = class {};
每个文件的 JSX 工厂
¥Per-file JSX factories
TypeScript 2.8 添加了使用 @jsx dom
指令在每个文件上配置 JSX 工厂名称的支持。JSX 工厂可以配置为使用 jsxFactory
(默认为 React.createElement
)进行编译。在 TypeScript 2.8 中,你可以通过在文件开头添加注释来逐个文件地覆盖此功能。
¥TypeScript 2.8 adds support for a per-file configurable JSX factory name using @jsx dom
pragma.
JSX factory can be configured for a compilation using jsxFactory
(default is React.createElement
). With TypeScript 2.8 you can override this on a per-file-basis by adding a comment to the beginning of the file.
示例
¥Example
ts
/** @jsx dom */import { dom } from "./renderer";<h></h>;
生成:
¥Generates:
js
var renderer_1 = require("./renderer");renderer_1.dom("h", null);
本地作用域的 JSX 命名空间
¥Locally scoped JSX namespaces
JSX 类型检查由 JSX 命名空间中的定义驱动,例如,JSX.Element
表示 JSX 元素的类型,JSX.IntrinsicElements
表示内置元素的类型。在 TypeScript 2.8 之前,JSX
命名空间应该位于全局命名空间中,因此一个项目中只能定义一个 JSX
命名空间。从 TypeScript 2.8 开始,JSX
命名空间将在 jsxNamespace
(例如 React
)下查找,从而允许在一次编译中使用多个 jsx 工厂。为了向后兼容,如果工厂函数中未定义全局 JSX
命名空间,则使用全局命名空间作为后备。结合每个文件的 @jsx
指令,每个文件可以拥有不同的 JSX 工厂。
¥JSX type checking is driven by definitions in a JSX namespace, for instance JSX.Element
for the type of a JSX element, and JSX.IntrinsicElements
for built-in elements.
Before TypeScript 2.8 the JSX
namespace was expected to be in the global namespace, and thus only allowing one to be defined in a project.
Starting with TypeScript 2.8 the JSX
namespace will be looked under the jsxNamespace
(e.g. React
) allowing for multiple jsx factories in one compilation.
For backward compatibility the global JSX
namespace is used as a fallback if none was defined on the factory function.
Combined with the per-file @jsx
pragma, each file can have a different JSX factory.
新的 --emitDeclarationOnly
¥New --emitDeclarationOnly
emitDeclarationOnly
仅允许生成声明文件;使用此标志将跳过 .js
/.jsx
输出生成。当 .js
输出生成由其他转译器(例如 Babel)处理时,此标志很有用。
¥emitDeclarationOnly
allows for only generating declaration files; .js
/.jsx
output generation will be skipped with this flag. The flag is useful when the .js
output generation is handled by a different transpiler like Babel.