Skip to content

Translate _Type Manipulation_ Into Chinese #120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 16 commits into from
Closed
60 changes: 60 additions & 0 deletions docs/documentation/zh/handbook-v2/The Handbook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
title: The TypeScript Handbook
layout: docs
permalink: /zh/docs/handbook/intro.html
oneline: 初探 TypeScript
handbook: "true"
---

## 关于这本手册

JavaScript 被引入编程社区 20 多年后,现在已成为有史以来最广泛使用的跨平台语言之一。JavaScript 最初是一种小型脚本语言,用于为网页添加零星的交互性,现在已成为各种规模的前端和后端应用程序的首选语言。随着使用 JavaScript 编写的程序的规模、领域和复杂度指数级增长,其根本不具备能力来表达不同单元代码之间的相关性。再加上 JavaScript 特有的运行时语义,这种语言与程序复杂性之间的不搭调,让 JavaScript 的开发成为了难以进行大规模管理的任务。

程序员写代码最典型的错误可以称为类型错误:一个明确类型的值被用在了期望是别的类型的值的地方。这可能只是一个简单的打字疏误,对库外层 API 的误解,对运行时行为的不当猜测,抑或别的错误。TypeScript 的目的就是成为 JavaScript 程序的静态类型检查器 - 换而言之,是一个在您的代码运行(静态的)之前运行的,以确保程序的类型时正确的工具(已类型检查的)。

如果您是在没有 JavaScript 背景下接触 TypeScript 的,意图让 TypeScript 成为您的第一个开发语言,我们推荐您首先阅读这些文档 [Microsoft Learn JavaScript tutorial](https://docs.microsoft.com/javascript/) 或者 [JavaScript at the Mozilla Web Docs](https://developer.mozilla.org/docs/Web/JavaScript/Guide)。
如果您有其他语言的开发经验,您应该通过阅读本手册就能来非常快速地掌握 JavaScript 语法

## 本操作手册的结构

本操作手册分为两个章节:

- **手册**

TypeScript 手册有意作为一份综合性的文档,来向日常程序员们解释 TypeScript。您可以在左侧导航栏中从上到下阅读手册。

你应该期望每一章或每一页都能让你对给定的概念有一个深刻的理解。TypeScript 手册不是一个完整的语言规范,但它旨在全面指导语言的所有特性和行为。

完成演练的读者应能够:

- 阅读并理解常用的 TypeScript 语法和模式
- 解释重要编译器选项的效果
- 在大多数情况下,正确预测类型系统行为

为了清晰和简洁起见,本手册的主要内容不会探讨所涵盖特征的每一个边缘情况或细节。您可以在参考文章中找到有关特定概念的更多详细信息。

- **参考文件**

导航中手册下方的参考部分旨在更深入地了解 TypeScript 的特定部分是如何工作的。你可以自上而下地阅读,但每一部分的目的都是对一个概念进行更深入的解释——这意味着没有连续性的目标。

### 非目标

该手册也是一份简明的文件,可以在几个小时内轻松阅读。为了保持简短,某些主题将不会被涵盖。

具体来说,该手册没有完全介绍核心 JavaScript 基础知识,如函数、类和闭包。在适当的情况下,我们将包括背景阅读的链接,您可以使用这些链接来阅读这些概念。

本手册也无意取代语言规范。在某些情况下,边缘案例或行为的正式描述将被跳过,以支持更高层次、更容易理解的解释。相反,有单独的参考页,更精确和正式地描述TypeScript行为的许多方面。参考页不适用于不熟悉TypeScript的读者,因此它们可能会使用您尚未阅读过的高级术语或参考主题。

最后,除了必要的地方,本手册不会介绍 TypeScript 如何与其他工具交互。诸如如何使用 webpack、rollup、packet、react、babel、closure、lerna、rush、bazel、preact、vue、angular、svelte、jquery、warn 或 npm 配置 TypeScript 等主题不在讨论范围之内-您可以在web上的其他地方找到这些资源。

## 入门
在开始学习[基础知识](/docs/handbook/2/basic types.html)之前,我们建议先阅读以下介绍页面之一。这些介绍旨在强调 TypeScript 和您喜欢的编程语言之间的关键相似性和差异,并澄清这些语言特有的常见误解。



- [TypeScript for New Programmers](/docs/handbook/typescript-from-scratch.html)
- [TypeScript for JavaScript Programmers](/docs/handbook/typescript-in-5-minutes.html)
- [TypeScript for OOP Programmers](/docs/handbook/typescript-in-5-minutes-oop.html)
- [TypeScript for Functional Programmers](/docs/handbook/typescript-in-5-minutes-func.html)

否则跳转至 [The Basics](/docs/handbook/2/basic-types.html) 或者在[Epub](/assets/typescript-handbook.epub) 获取副本或者 [PDF](/assets/typescript-handbook.pdf) 形式。
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
---
title: Conditional Types
layout: docs
permalink: /zh/docs/handbook/2/conditional-types.html
oneline: "在类型系统中像 if 语句那样的类型"
---

在最有用的程序中,我们必须根据输入做出决定。
JavaScript 程序也不例外,但鉴于值可以很容易地进行内省,这些决策也基于输入的类型。
_条件类型_ 帮助描述输入和输出类型之间的关系。

```ts twoslash
interface Animal {
live(): void;
}
interface Dog extends Animal {
woof(): void;
}

type Example1 = Dog extends Animal ? number : string;
// ^?

type Example2 = RegExp extends Animal ? number : string;
// ^?
```

条件类型采用的这种形式看起来有点像 JavaScript 中的条件表达式 (`condition ? trueExpression : falseExpression`) :

```ts twoslash
type SomeType = any;
type OtherType = any;
type TrueType = any;
type FalseType = any;
type Stuff =
// ---cut---
SomeType extends OtherType ? TrueType : FalseType;
```
当 `extends` 左侧的类型可以分配给右侧这个是,那么您将获取第一个分支上的类型(即「true」分支);否则您将获取在后面分支上的类型(即「false」分支)。

从上面的例子来看,条件类型可能不会立即变得有用 -- 我们可以告诉自己 `Dog extensed Animal` 是否成立,然后选择 `number` 或 `string`!
但是条件类型的威力来自于将它们与泛型一起使用。

举个例子,我们来看下面的 `createLabel` 函数:

```ts twoslash
interface IdLabel {
id: number /* some fields */;
}
interface NameLabel {
name: string /* other fields */;
}

function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
throw "unimplemented";
}
```
`createLabel` 函数的这些重载,描述的一个单独的 JavaScript 函数,此函数根据输入的类型有不同的选项。 注意这几点:

1. 如果一个库必须在整个 API 中反复做出相同的选项,那么这将变得很麻烦。
2. 我们不得不创建三个重载:一个是我们已经确定类型的各个情形 (其一是 `string` ,另一个是 `number`),然后另一个是最通用的情形 (接收一个 `string | number`)。对于 `createLabel` 可以处理的每一种新类型,重载的数量都会呈指数增长。

代之,我们可以通过条件类型来编写这段逻辑:

```ts twoslash
interface IdLabel {
id: number /* some fields */;
}
interface NameLabel {
name: string /* other fields */;
}
// ---cut---
type NameOrId<T extends number | string> = T extends number
? IdLabel
: NameLabel;
```

之后,我们可以使用该条件类型将重载简化为一个没有重载的函数。

```ts twoslash
interface IdLabel {
id: number /* some fields */;
}
interface NameLabel {
name: string /* other fields */;
}
type NameOrId<T extends number | string> = T extends number
? IdLabel
: NameLabel;
// ---cut---
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
throw "unimplemented";
}

let a = createLabel("typescript");
// ^?

let b = createLabel(2.8);
// ^?

let c = createLabel(Math.random() ? "hello" : 42);
// ^?
```

### 条件类型约束

通常,条件类型中的检查会为我们提供一些新信息。
就像使用类型保护,缩小范围可以为我们提供更具体的类型一样,条件类型的 true 分支将通过我们检查的类型进一步约束泛型。

举例, 我们看下面代码:

```ts twoslash
// @errors: 2536
type MessageOf<T> = T["message"];
```

在这个示例中,TypeScript 报错了,因为 `T` 无法判断其有名为 `message` 的属性。
我们可以约束 `T`,TypeScript 就不再报错了:

```ts twoslash
type MessageOf<T extends { message: unknown }> = T["message"];

interface Email {
message: string;
}

type EmailMessageContents = MessageOf<Email>;
// ^?
```

然而,我们如果想要 `MessageOf` 能接受任意的类型,并且如果其没有 `message` 属性,那就是一个默认类型,就像 `never` ,这该怎么办呢?

我们可以通过将约束移出并引入条件类型来实现这一点:

```ts twoslash
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;

interface Email {
message: string;
}

interface Dog {
bark(): void;
}

type EmailMessageContents = MessageOf<Email>;
// ^?

type DogMessageContents = MessageOf<Dog>;
// ^?
```

在 true 分支中, TypeScript 明白 `T` _将_ 有 `message` 属性。

作为另一个示例,我们还可以编写一个名为 `Flatten` 的类型,将数组类型展平为其元素类型,但在其他情况下不使用它们:

```ts twoslash
type Flatten<T> = T extends any[] ? T[number] : T;

// Extracts out the element type.
type Str = Flatten<string[]>;
// ^?

// Leaves the type alone.
type Num = Flatten<number>;
// ^?
```

当 `Flatten` 接收一个数组类型,它通过使用 `number` 索引访问以获取 `string[]` 的元素类型。
否则,它仅返回传入给它的类型。

### 条件类型内的推断

我们只是发现自己使用条件类型来应用约束,然后提取类型。
这是一个非常常见的操作,条件类型使它变得更容易。

条件类型为我们提供了一种从 `true` 分支中比较的类型中进行推断的方法,就是使用 `infer` 关键字。
例如,我们可以推断出在 `Flatten` 中元素的类型,而不是用索引访问类型「手动」获取它:

```ts twoslash
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
```

这里,我们使用 `infer` 关键词去声明性地引入一个新的泛型变量 `Item`,而不是指定如何如何在 true 分支中去会 `T` 元素类型。
这使得我们不必考虑怎样去发掘和探究我们感兴趣的类型结构了。


运用 `infer` 关键词,我们可以写一些实用的辅助类型别称。
例如,对于简单的场景,我们可以从函数类型中提取出返回类型。

```ts twoslash
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
? Return
: never;

type Num = GetReturnType<() => number>;
// ^?

type Str = GetReturnType<(x: string) => string>;
// ^?

type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
// ^?
```

当从一个具有多个调用签名的类型(比如一个重载函数的类型)中进行推断时,是从 _最后_ 一项签名中(这项可能是最宽泛的情形)做推断的。无法基于参数类型列表来执行重载的解析。

```ts twoslash
declare function stringOrNum(x: string): number;
declare function stringOrNum(x: number): string;
declare function stringOrNum(x: string | number): string | number;

type T1 = ReturnType<typeof stringOrNum>;
// ^?
```

## 分配条件类型

当条件类型作用于泛型类型时,当给定一个联合类型时,它们就变成了 _分配的_。
例如,看下面:

```ts twoslash
type ToArray<Type> = Type extends any ? Type[] : never;
```

如果我们将一个联合类型插入 `ToArray`,那么条件类型将应用于该联合类型中的每个成员。

```ts twoslash
type ToArray<Type> = Type extends any ? Type[] : never;

type StrArrOrNumArr = ToArray<string | number>;
// ^?
```

然后 `StrArrOrNumArr` 却是这样分配类型的:

```ts twoslash
type StrArrOrNumArr =
// ---cut---
string | number;
```

需要把联合类型中的每个成员类型都映射进来,这样才能生效:

```ts twoslash
type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr =
// ---cut---
ToArray<string> | ToArray<number>;
```

就像这样的:

```ts twoslash
type StrArrOrNumArr =
// ---cut---
string[] | number[];
```

通常,分配性是预期的行为。
为了避免这种行为,可以用方括号括住 extends 关键字的每一侧。

```ts twoslash
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;

// 'StrArrOrNumArr' 就不在是联合类型。
type StrArrOrNumArr = ToArrayNonDist<string | number>;
// ^?
```
Loading