# TypeScript 2.8

## 有条件类型

TypeScript 2.8引入了*有条件类型*，它能够表示非统一的类型。 有条件的类型会以一个条件表达式进行类型关系检测，从而在两种类型中选择其一：

```typescript
T extends U ? X : Y
```

上面的类型意思是，若`T`能够赋值给`U`，那么类型是`X`，否则为`Y`。

有条件的类型`T extends U ? X : Y`或者*解析*为`X`，或者*解析*为`Y`，再或者*延迟*解析，因为它可能依赖一个或多个类型变量。 是否直接解析或推迟取决于：

* 首先，令`T'`和`U'`分别为`T`和`U`的实例，并将所有类型参数替换为`any`，如果`T'`不能赋值给`U'`，则将有条件的类型解析成`Y`。直观上讲，如果最宽泛的`T`的实例不能赋值给最宽泛的`U`的实例，那么我们就可以断定不存在可以赋值的实例，因此可以解析为`Y`。
* 其次，针对每个在`U`内由`推断`声明引入的类型变量，依据从`T`推断到`U`来收集一组候选类型（使用与泛型函数类型推断相同的推断算法）。对于给定的`推断`类型变量`V`，如果有候选类型是从协变的位置上推断出来的，那么`V`的类型是那些候选类型的联合。反之，如果有候选类型是从逆变的位置上推断出来的，那么`V`的类型是那些候选类型的交叉类型。否则`V`的类型是`never`。
* 然后，令`T''`为`T`的一个实例，所有`推断`的类型变量用上一步的推断结果替换，如果`T''`*明显可赋值*给`U`，那么将有条件的类型解析为`X`。除去不考虑类型变量的限制之外，*明显可赋值*的关系与正常的赋值关系一致。直观上，当一个类型明显可赋值给另一个类型，我们就能够知道它可以赋值给那些类型的*所有*实例。
* 否则，这个条件依赖于一个或多个类型变量，有条件的类型解析被推迟进行。

#### 例子

```typescript
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"
```

### 分布式有条件类型

如果有条件类型里待检查的类型是`naked type parameter`，那么它也被称为“分布式有条件类型”。 分布式有条件类型在实例化时会自动分发成联合类型。 例如，实例化`T extends U ? X : Y`，`T`的类型为`A | B | C`，会被解析为`(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)`。

#### 例子

```typescript
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`）。

#### 例子

```typescript
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>;
```

注意在`Boxed<T>`的`true`分支里，`T`有个额外的约束`any[]`，因此它适用于`T[number]`数组元素类型。同时也注意一下有条件类型是如何分布成联合类型的。

有条件类型的分布式的属性可以方便地用来*过滤*联合类型：

```typescript
type Diff<T, U> = T extends U ? never : T;  // Remove types from T that are assignable to U
type Filter<T, U> = T extends U ? T : never;  // Remove types from T that are not assignable to U

type 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 | number
type T33 = Filter<string | number | (() => void), Function>;  // () => void

type NonNullable<T> = Diff<T, null | undefined>;  // Remove null and undefined from T

type T34 = NonNullable<string | number | undefined>;  // string | number
type T35 = NonNullable<string | string[] | null | undefined>;  // string | string[]

function f1<T>(x: T, y: NonNullable<T>) {
    x = y;  // Ok
    y = x;  // Error
}

function f2<T extends string | undefined>(x: T, y: NonNullable<T>) {
    x = y;  // Ok
    y = x;  // Error
    let s1: string = x;  // Error
    let s2: string = y;  // Ok
}
```

有条件类型与映射类型结合时特别有用：

```typescript
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[] }
```

与联合类型和交叉类型相似，有条件类型不允许递归地引用自己。比如下面的错误。

#### 例子

```typescript
type ElementType<T> = T extends any[] ? ElementType<T[number]> : T;  // Error
```

### 有条件类型中的类型推断

现在在有条件类型的`extends`子语句中，允许出现`infer`声明，它会引入一个待推断的类型变量。 这个推断的类型变量可以在有条件类型的true分支中被引用。 允许出现多个同类型变量的`infer`。

例如，下面代码会提取函数类型的返回值类型：

```typescript
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
```

有条件类型可以嵌套来构成一系列的匹配模式，按顺序进行求值：

```typescript
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>;  // string
type T1 = Unpacked<string[]>;  // string
type T2 = Unpacked<() => string>;  // string
type T3 = Unpacked<Promise<string>>;  // string
type T4 = Unpacked<Promise<string>[]>;  // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>>;  // string
```

下面的例子解释了在协变位置上，同一个类型变量的多个候选类型会被推断为联合类型：

```typescript
type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;
type T10 = Foo<{ a: string, b: string }>;  // string
type T11 = Foo<{ a: string, b: number }>;  // string | number
```

相似地，在抗变位置上，同一个类型变量的多个候选类型会被推断为交叉类型：

```typescript
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 }>;  // string
type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>;  // string & number
```

当推断具有多个调用签名（例如函数重载类型）的类型时，用*最后*的签名（大概是最自由的包含所有情况的签名）进行推断。 无法根据参数类型列表来解析重载。

```typescript
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`声明：

```typescript
type ReturnType<T extends (...args: any[]) => infer R> = R;  // 错误，不支持
```

但是，可以这样达到同样的效果，在约束里删掉类型变量，用有条件类型替换：

```typescript
type AnyFunction = (...args: any[]) => any;
type ReturnType<T extends AnyFunction> = T extends (...args: any[]) => infer R ? R : any;
```

### 预定义的有条件类型

TypeScript 2.8在`lib.d.ts`里增加了一些预定义的有条件类型：

* `Exclude<T, U>` -- 从`T`中剔除可以赋值给`U`的类型。
* `Extract<T, U>` -- 提取`T`中可以赋值给`U`的类型。
* `NonNullable<T>` -- 从`T`中剔除`null`和`undefined`。
* `ReturnType<T>` -- 获取函数返回值类型。
* `InstanceType<T>` -- 获取构造函数类型的实例类型。

#### Example

```typescript
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 | number
type T03 = Extract<string | number | (() => void), Function>;  // () => void

type T04 = NonNullable<string | number | undefined>;  // string | number
type 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>;  // string
type T11 = ReturnType<(s: string) => void>;  // void
type 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>;  // any
type T16 = ReturnType<never>;  // any
type T17 = ReturnType<string>;  // Error
type T18 = ReturnType<Function>;  // Error

type T20 = InstanceType<typeof C>;  // C
type T21 = InstanceType<any>;  // any
type T22 = InstanceType<never>;  // any
type T23 = InstanceType<string>;  // Error
type T24 = InstanceType<Function>;  // Error
```

> 注意：`Exclude`类型是[建议的](https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458)`Diff`类型的一种实现。我们使用`Exclude`这个名字是为了避免破坏已经定义了`Diff`的代码，并且我们感觉这个名字能更好地表达类型的语义。我们没有增加`Omit<T, K>`类型，因为它可以很容易的用`Pick<T, Exclude<keyof T, K>>`来表示。

## 改进对映射类型修饰符的控制

映射类型支持在属性上添加`readonly`或`?`修饰符，但是它们不支持*移除*修饰符。 这对于[*同态映射类型*](https://github.com/Microsoft/TypeScript/pull/12563)有些影响，因为同态映射类型默认保留底层类型的修饰符。

TypeScript 2.8为映射类型增加了增加或移除特定修饰符的能力。 特别地，映射类型里的`readonly`或`?`属性修饰符现在可以使用`+`或`-`前缀，来表示修饰符是添加还是移除。

#### 例子

```typescript
type MutableRequired<T> = { -readonly [P in keyof T]-?: T[P] };  // 移除readonly和?
type ReadonlyPartial<T> = { +readonly [P in keyof T]+?: T[P] };  // 添加readonly和?
```

不带`+`或`-`前缀的修饰符与带`+`前缀的修饰符具有相同的作用。因此上面的`ReadonlyPartial<T>`类型与下面的一致

```typescript
type ReadonlyPartial<T> = { readonly [P in keyof T]?: T[P] };  // 添加readonly和?
```

利用这个特性，`lib.d.ts`现在有了一个新的`Required<T>`类型。 它移除了`T`的所有属性的`?`修饰符，因此所有属性都是必需的。

#### 例子

```typescript
type Required<T> = { [P in keyof T]-?: T[P] };
```

注意在`--strictNullChecks`模式下，当同态映射类型移除了属性底层类型的`?`修饰符，它同时也移除了那个属性上的`undefined`类型：

#### 例子

```typescript
type Foo = { a?: string };  // 等同于 { a?: string | undefined }
type Bar = Required<Foo>;  // 等同于 { a: string }
```

## 改进交叉类型上的`keyof`

TypeScript 2.8作用于交叉类型的`keyof`被转换成作用于交叉成员的`keyof`的联合。 换句话说，`keyof (A & B)`会被转换成`keyof A | keyof B`。 这个改动应该能够解决`keyof`表达式推断不一致的问题。

#### 例子

```typescript
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 U
type T4<T, U> = keyof (T & U);  // keyof T | keyof U
type T5 = T2<A>;  // "a" | "b"
type T6 = T3<B>;  // "a" | "b"
type T7 = T4<A, B>;  // "a" | "b"
```

## 更好的处理`.js`文件中的命名空间模式

TypeScript 2.8加强了识别`.js`文件里的命名空间模式。 JavaScript顶层的空对象字面量声明，就像函数和类，会被识别成命名空间声明。

```javascript
var ns = {};     // recognized as a declaration for a namespace `ns`
ns.constant = 1; // recognized as a declaration for var `constant`
```

顶层的赋值应该有一致的行为；也就是说，`var`或`const`声明不是必需的。

```javascript
app = {}; // does NOT need to be `var app = {}`
app.C = class {
};
app.f = function() {
};
app.prop = 1;
```

### 立即执行的函数表达式做为命名空间

立即执行的函数表达式返回一个函数，类或空的对象字面量，也会被识别为命名空间：

```javascript
var C = (function () {
  function C(n) {
    this.p = n;
  }
  return C;
})();
C.staticProperty = 1;
```

### 默认声明

“默认声明”允许引用了声明的名称的初始化器出现在逻辑或的左边：

```javascript
my = window.my || {};
my.app = my.app || {};
```

### 原型赋值

你可以把一个对象字面量直接赋值给原型属性。独立的原型赋值也可以：

```typescript
var C = function (p) {
  this.p = p;
};
C.prototype = {
  m() {
    console.log(this.p);
  }
};
C.prototype.q = function(r) {
  return this.p === r;
};
```

### 嵌套与合并声明

现在嵌套的层次不受限制，并且多文件之间的声明合并也没有问题。以前不是这样的。

```javascript
var app = window.app || {};
app.C = class { };
```

## 各文件的JSX工厂

TypeScript 2.8增加了使用`@jsx dom`指令为每个文件设置JSX工厂名。 JSX工厂也可以使用`--jsxFactory`编译参数设置（默认值为`React.createElement`）。TypeScript 2.8你可以基于文件进行覆写。

#### 例子

```typescript
/** @jsx dom */
import { dom } from "./renderer"
<h></h>
```

生成：

```javascript
var renderer_1 = require("./renderer");
renderer_1.dom("h", null);
```

## 本地范围的JSX命名空间

JSX类型检查基于JSX命名空间里的定义，比如`JSX.Element`用于JSX元素的类型，`JSX.IntrinsicElements`用于内置的元素。 在TypeScript 2.8之前`JSX`命名空间被视为全局命名空间，并且一个工程只允许存在一个。 TypeScript 2.8开始，`JSX`命名空间将在`jsxNamespace`下面查找（比如`React`），允许在一次编译中存在多个jsx工厂。 为了向后兼容，全局的`JSX`命名空间被当做回退选项。 使用独立的`@jsx`指令，每个文件可以有自己的JSX工厂。

## 新的`--emitDeclarationsOnly`

`--emitDeclarationsOnly`允许*仅*生成声明文件；使用这个标记`.js`/`.jsx`输出会被跳过。当使用其它的转换工具如Babel处理`.js`输出的时候，可以使用这个标记。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://monksoul.gitbook.io/typescript/release-notes/typescript-2.8.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
