diff --git a/docs/documentation/zh/handbook-v2/The Handbook.md b/docs/documentation/zh/handbook-v2/The Handbook.md new file mode 100644 index 00000000..8b022bbe --- /dev/null +++ b/docs/documentation/zh/handbook-v2/The Handbook.md @@ -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) 形式。 diff --git a/docs/documentation/zh/handbook-v2/Type Manipulation/Conditional Types.md b/docs/documentation/zh/handbook-v2/Type Manipulation/Conditional Types.md new file mode 100644 index 00000000..bcbacc23 --- /dev/null +++ b/docs/documentation/zh/handbook-v2/Type Manipulation/Conditional Types.md @@ -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 + ? IdLabel + : NameLabel; +``` + +之后,我们可以使用该条件类型将重载简化为一个没有重载的函数。 + +```ts twoslash +interface IdLabel { + id: number /* some fields */; +} +interface NameLabel { + name: string /* other fields */; +} +type NameOrId = T extends number + ? IdLabel + : NameLabel; +// ---cut--- +function createLabel(idOrName: T): NameOrId { + 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["message"]; +``` + +在这个示例中,TypeScript 报错了,因为 `T` 无法判断其有名为 `message` 的属性。 +我们可以约束 `T`,TypeScript 就不再报错了: + +```ts twoslash +type MessageOf = T["message"]; + +interface Email { + message: string; +} + +type EmailMessageContents = MessageOf; +// ^? +``` + +然而,我们如果想要 `MessageOf` 能接受任意的类型,并且如果其没有 `message` 属性,那就是一个默认类型,就像 `never` ,这该怎么办呢? + +我们可以通过将约束移出并引入条件类型来实现这一点: + +```ts twoslash +type MessageOf = T extends { message: unknown } ? T["message"] : never; + +interface Email { + message: string; +} + +interface Dog { + bark(): void; +} + +type EmailMessageContents = MessageOf; +// ^? + +type DogMessageContents = MessageOf; +// ^? +``` + +在 true 分支中, TypeScript 明白 `T` _将_ 有 `message` 属性。 + +作为另一个示例,我们还可以编写一个名为 `Flatten` 的类型,将数组类型展平为其元素类型,但在其他情况下不使用它们: + +```ts twoslash +type Flatten = T extends any[] ? T[number] : T; + +// Extracts out the element type. +type Str = Flatten; +// ^? + +// Leaves the type alone. +type Num = Flatten; +// ^? +``` + +当 `Flatten` 接收一个数组类型,它通过使用 `number` 索引访问以获取 `string[]` 的元素类型。 +否则,它仅返回传入给它的类型。 + +### 条件类型内的推断 + +我们只是发现自己使用条件类型来应用约束,然后提取类型。 +这是一个非常常见的操作,条件类型使它变得更容易。 + +条件类型为我们提供了一种从 `true` 分支中比较的类型中进行推断的方法,就是使用 `infer` 关键字。 +例如,我们可以推断出在 `Flatten` 中元素的类型,而不是用索引访问类型「手动」获取它: + +```ts twoslash +type Flatten = Type extends Array ? Item : Type; +``` + +这里,我们使用 `infer` 关键词去声明性地引入一个新的泛型变量 `Item`,而不是指定如何如何在 true 分支中去会 `T` 元素类型。 +这使得我们不必考虑怎样去发掘和探究我们感兴趣的类型结构了。 + + +运用 `infer` 关键词,我们可以写一些实用的辅助类型别称。 +例如,对于简单的场景,我们可以从函数类型中提取出返回类型。 + +```ts twoslash +type GetReturnType = 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; +// ^? +``` + +## 分配条件类型 + +当条件类型作用于泛型类型时,当给定一个联合类型时,它们就变成了 _分配的_。 +例如,看下面: + +```ts twoslash +type ToArray = Type extends any ? Type[] : never; +``` + +如果我们将一个联合类型插入 `ToArray`,那么条件类型将应用于该联合类型中的每个成员。 + +```ts twoslash +type ToArray = Type extends any ? Type[] : never; + +type StrArrOrNumArr = ToArray; +// ^? +``` + +然后 `StrArrOrNumArr` 却是这样分配类型的: + +```ts twoslash +type StrArrOrNumArr = + // ---cut--- + string | number; +``` + +需要把联合类型中的每个成员类型都映射进来,这样才能生效: + +```ts twoslash +type ToArray = Type extends any ? Type[] : never; +type StrArrOrNumArr = + // ---cut--- + ToArray | ToArray; +``` + +就像这样的: + +```ts twoslash +type StrArrOrNumArr = + // ---cut--- + string[] | number[]; +``` + +通常,分配性是预期的行为。 +为了避免这种行为,可以用方括号括住 extends 关键字的每一侧。 + +```ts twoslash +type ToArrayNonDist = [Type] extends [any] ? Type[] : never; + +// 'StrArrOrNumArr' 就不在是联合类型。 +type StrArrOrNumArr = ToArrayNonDist; +// ^? +``` diff --git a/docs/documentation/zh/handbook-v2/Type Manipulation/Generics.md b/docs/documentation/zh/handbook-v2/Type Manipulation/Generics.md new file mode 100644 index 00000000..95483381 --- /dev/null +++ b/docs/documentation/zh/handbook-v2/Type Manipulation/Generics.md @@ -0,0 +1,384 @@ +--- +title: Generics +layout: docs +permalink: /zh/docs/handbook/2/generics.html +oneline: 接收参数的类型 +--- + +软件工程的一个主要部分是构建组件,这些组件不仅具有定义良好且一致的 API,而且还具有可复用性。 +那些既可以处理现在的数据,也可以处理将来的数据的组件,使您在构建大型软件系统灵活自如。 + +在类似 C# 和 Java 的语言中,创建可复用性组件的一大法宝就是 _泛型_,也就是说,能够创建可以多种类型而非单一类型上工作的组件。 +它允许用户可以按照他们自己的类型使用这些组件。 +## 泛型 Hello World + +首先,让我们通过 identity 函数做一个泛型的 「hello world」: +identity 函数是一个返回传入内容的函数。 +您可以将其想象为类似 `echo` 命令的方法。 + +没有泛型的情况下,我们可能必须要给这个 `identity` 函数指定类型: + +```ts twoslash +function identity(arg: number): number { + return arg; +} +``` + +或者,我们可以用 `any` 类型来表示这个 identity 函数: + +```ts twoslash +function identity(arg: any): any { + return arg; +} +``` + +因为使用 `any` 肯定是宽泛的,这导致该函数的 `arg` 可以接收任意和全部的类型,我们实际上就无法得知该函数返回值的类型信息。 +如果我们传入一个数字,我们仅能得到的信息就是,其可能会返回任意类型。 + +相反的,我们需要一种途径去得知参数的类型,从而我们可以利用它去表示将返回何种类型。 +在此,我们将使用一个 _类型变量_ ,它是一种工作在类型系统中的变量,而非一般的值。 + +```ts twoslash +function identity(arg: Type): Type { + return arg; +} +``` + +我们刚刚给 identity 函数添加了一个类型变量 `Type`。 +这个 `Type` 允许我们捕获用户传递的参数类型(如 `number`),于是我们待会儿要用到这个信息。 +这里,我们再次使用 `Type` 作为返回类型。通过检查,我们现在可以看到参数和返回类型使用了相同的类型。 +这让我们将类型信息从函数的一侧,传达到其另一侧。 + +我们将这种版本的 `identity` 函数称为泛型,因为它适用于一系列类型。 +与使用 `any` 不一样,这种方式与使用数字作为参数和返回值的那个第一个 `identity` 函数一样精确(如:它不会丢失任何信息)。 + +当我们写了这个泛型 identity 函数,我们可以通过两种方式去调用它。 +第一种方式是将所有的参数,包括类型的参数,传递给函数: + +```ts twoslash +function identity(arg: Type): Type { + return arg; +} +// ---cut--- +let output = identity("myString"); +// ^? +``` + +在这里,我们显式地将 `Type` 设置为 `string`,作为函数调用的参数之一,在参数周围使用 `<>` 表示,而不是使用 `()`。 + +第二种方式也许最常见。这里我们使用 _类型参数推导_ -- 也就是,我们想要编译器通过我们所传参数的类型,去自动为我们设置 `Type` 的值: + +```ts twoslash +function identity(arg: Type): Type { + return arg; +} +// ---cut--- +let output = identity("myString"); +// ^? +``` + +注意,我们不必显式地传递类型到单方括号(`<>`)里;编译器会顾及 `"myString"` 的值,并设置 `Type` 为其类型。 +虽然类型参数推断是保持代码更短和更可读的有用工具,但当编译器无法推断类型时,您可能需要显式地传递类型参数,就像我们在上一个示例中所做的那样,这可能发生在更复杂的示例中。 +## 使用通用类型变量 + +当您开始使用泛型,您将会注意到,当您创建一个像 `identify` 这样的通用函数时,编译器会强制您在函数体内正确的使用任意的通用类型参数。 +也就是说,您实际上可以将这些参数视为任何类型。 + +让我们回头看一下 `identity` 函数: + +```ts twoslash +function identity(arg: Type): Type { + return arg; +} +``` + +我们要是想每次调用时,顺带把参数 `arg` 的长度打印到控制台会怎样? +我们暂且写成这样: + +```ts twoslash +// @errors: 2339 +function loggingIdentity(arg: Type): Type { + console.log(arg.length); + return arg; +} +``` + +当我们这样写,编译器将会给我们报错,说我们正在使用 `arg` 的 `.length` 成员,但是我们并没有说过 `arg` 上有该成员。 + +请记住,正如我们之前谈及,这些类型变量表示任意和全部的类型,因此使用此函数的人可能会传入一个没有 `.length` 成员的 `number`。 + +我们说,我们实际上是打算这个函数是使用 `Type` 数组,而非直接使用 `Type`。当我们所使用的是数组,才可能有 `.length` 成员。 +我们可以像我们将创建其他类型的数组那样描述它: + +```ts twoslash {1} +function loggingIdentity(arg: Type[]): Type[] { + console.log(arg.length); + return arg; +} +``` + +我们可以把 `loggingIdentity` 的类型理解成 「通用函数 `loggingIdentity` 接收一个类型参数 `Type`,以及一个参数 `arg`, 该参数是一个以 `Type` 为元素的数组类型,然后返回值是 `Type` 数组」。 + +我们如果传递一个数字数组,我们将得到一个数字数组返回,因为 `Type` 被绑定为 `number`。 + +这使得我们可以使用类型变量 `type` 作为我们正在处理的类型中的一部分,而不是整个类型,给了我们更大的灵活性。 + +我们也可以这样编写示例: + +```ts twoslash {1} +function loggingIdentity(arg: Array): Array { + console.log(arg.length); // Array has a .length, so no more error + return arg; +} +``` + +这样的类型样式您可能在别的语言中似曾相识。 +下一章节,我们将介绍如何创建一个您自己的一个像 `Array` 这样的泛型。 + +## 通用类型 + +在上一章节中,我们创建了一个通用函数 `identity`,它适用于一系列类型。 +本章节我们将探索函数自身的类型,以及如何创建通用接口。 + +通用函数的类型就和那些非通用函数差不多,首先列出类型参数,就好像声明一个函数: + +```ts twoslash +function identity(arg: Type): Type { + return arg; +} + +let myIdentity: (arg: Type) => Type = identity; +``` + +我们也可能在为泛型的类型参数使用不同的名称,只要类型变量的个数与类型变量实际使用的一致即可。 + +```ts twoslash +function identity(arg: Type): Type { + return arg; +} + +let myIdentity: (arg: Input) => Input = identity; +``` + +我们也可以将通用类型签名,写成对象字面量那样: + +```ts twoslash +function identity(arg: Type): Type { + return arg; +} + +let myIdentity: { (arg: Type): Type } = identity; +``` + +这让我们开始编写第一个通用接口。 +让我们来从上面例子中提取对象字面量,在将其移至一个接口上: + +```ts twoslash +interface GenericIdentityFn { + (arg: Type): Type; +} + +function identity(arg: Type): Type { + return arg; +} + +let myIdentity: GenericIdentityFn = identity; +``` + +在类似的示例中,我们可能希望将泛型参数移动为整个接口的参数。 + +这让我们可以看到我们的泛型是什么类型(例如,`Dictionary` 而不仅仅是 `Dictionary`)。 + +这使类型参数对接口的所有其他成员可见。 + +```ts twoslash +interface GenericIdentityFn { + (arg: Type): Type; +} + +function identity(arg: Type): Type { + return arg; +} + +let myIdentity: GenericIdentityFn = identity; +``` + +请注意,我们对示例稍微做了点改动。 + +我们现在没有描述通用函数,而是有一个非通用函数签名,它是通用类型的一部分。 + +当我们使用 `GenericIdentityFn`,我们现在还要指定对应类型的参数(这里是:`number`),这有效的限定了底层调用签名将使用的内容。 +理解何时将类型参数直接放在调用签名上,以及何时放在接口它本身上,这将有助于描述类型的那个方面是泛型的。 + +除了通用接口,我们还可以创建通用类。 +请注意,不能创建泛型枚举和命名空间。 + +## 通用类 + +通用类形似通用接口。 +通用类在类名称后面的尖括号 `<>` 内,有一个通用类型参数列表。 + +```ts twoslash +// @strict: false +class GenericNumber { + zeroValue: NumType; + add: (x: NumType, y: NumType) => NumType; +} + +let myGenericNumber = new GenericNumber(); +myGenericNumber.zeroValue = 0; +myGenericNumber.add = function (x, y) { + return x + y; +}; +``` +这是对 `GenericNumber` 类的一种相当字面的用法,但是您可能已经注意到,并没有限制它只能使用 `number` 类型。 + +我们还可以使用 `string` 又或者其他更多复杂的对象。 + +```ts twoslash +// @strict: false +class GenericNumber { + zeroValue: NumType; + add: (x: NumType, y: NumType) => NumType; +} +// ---cut--- +let stringNumeric = new GenericNumber(); +stringNumeric.zeroValue = ""; +stringNumeric.add = function (x, y) { + return x + y; +}; + +console.log(stringNumeric.add(stringNumeric.zeroValue, "test")); +``` + +和使用接口一样,给类其本身传类型参数,可以确保类的所有属性都使用相同的类型。 + +正如我们在 [关于类的章节](/docs/handbook/2/classes.html) 中所述,一个类它的类型分两个方面:静态方面和实例方面。 + +通用类只适用于它们的实例方面而没有使用静态方面,所以当使用类时,静态成员就不能使用类的类型参数。 + +## 泛型限制 + +如果您还记得前面的一个示例,那么您有时可能希望编写一个通用函数,该函数可以在一组类型上工作,您对该组类型将具有的功能有一些了解。 +在我们的 `loggingIdentity` 示例中,我们希望能够访问 `arg` 的 `.length` 属性,但编译器无法证明每个类型都有一个 `.length` 属性,因此它警告我们不能这样假设。 + +```ts twoslash +// @errors: 2339 +function loggingIdentity(arg: Type): Type { + console.log(arg.length); + return arg; +} +``` + +我们不想使用任意和全部类型,而是寄希望于约束这个函数,让其可以使用任意和全部,同时**还**需要有 `.length` 属性。 +只要类型有这个成员,我们就允许使用它,但至少是改成员是必须的。 + +要做到这一点,我们必须将我们的需求列为 `类型` 的约束条件。 +为此,我们将创建一个描述约束的接口。 +在这里,我们将创建一个具有单个 `.length` 属性的接口,然后使用此接口和 `extends` 关键字来表示我们的约束: + +```ts twoslash +interface Lengthwise { + length: number; +} + +function loggingIdentity(arg: Type): Type { + console.log(arg.length); // Now we know it has a .length property, so no more error + return arg; +} +``` + +由于通用函数现在被约束了,所以它不再能使用任何和全部类型了: + +```ts twoslash +// @errors: 2345 +interface Lengthwise { + length: number; +} + +function loggingIdentity(arg: Type): Type { + console.log(arg.length); + return arg; +} +// ---cut--- +loggingIdentity(3); +``` + +相反,我们需要传入其类型具有所有必需属性的值: + +```ts twoslash +interface Lengthwise { + length: number; +} + +function loggingIdentity(arg: Type): Type { + console.log(arg.length); + return arg; +} +// ---cut--- +loggingIdentity({ length: 10, value: 3 }); +``` + +## 在泛型约束中使用类型参数 + +可以声明受其他类型参数约束的类型参数。 + +例如,这里我们希望从给定名称的对象中获取属性。 + +我们希望确保不会意外获取 `obj` 上不存在的属性,因此我们将在这两种类型之间放置一个约束: + +```ts twoslash +// @errors: 2345 +function getProperty(obj: Type, key: Key) { + return obj[key]; +} + +let x = { a: 1, b: 2, c: 3, d: 4 }; + +getProperty(x, "a"); +getProperty(x, "m"); +``` + +## 在泛型中使用 class 类型 + +当在 TypeScript 中使用泛型创建一个工厂函数,必须将 class 的构造函数引用到 class 的类型。比如, + +```ts twoslash +function create(c: { new (): Type }): Type { + return new c(); +} +``` + +更进一步的示例,使用 prototype 属性来推断和约束构造函数与类类型的实例端之间的关系。 + +```ts twoslash +// @strict: false +class BeeKeeper { + hasMask: boolean = true; +} + +class ZooKeeper { + nametag: string = "Mikle"; +} + +class Animal { + numLegs: number = 4; +} + +class Bee extends Animal { + keeper: BeeKeeper = new BeeKeeper(); +} + +class Lion extends Animal { + keeper: ZooKeeper = new ZooKeeper(); +} + +function createInstance(c: new () => A): A { + return new c(); +} + +createInstance(Lion).keeper.nametag; +createInstance(Bee).keeper.hasMask; +``` + +该模式得利于 [mixins](/docs/handbook/mixins.html) 设计模式。 diff --git a/docs/documentation/zh/handbook-v2/Type Manipulation/Indexed Access Types.md b/docs/documentation/zh/handbook-v2/Type Manipulation/Indexed Access Types.md new file mode 100644 index 00000000..23d52aed --- /dev/null +++ b/docs/documentation/zh/handbook-v2/Type Manipulation/Indexed Access Types.md @@ -0,0 +1,77 @@ +--- +title: Indexed Access Types +layout: docs +permalink: /zh/docs/handbook/2/indexed-access-types.html +oneline: "通过 `Type['a']` 语法访问一个类型的子集。" +--- + +我们可以使用 _索引访问类型_ 来查找另一个类型上的特定属性。 + +```ts twoslash +type Person = { age: number; name: string; alive: boolean }; +type Age = Person["age"]; +// ^? +``` + +索引类型本身就是一个类型,所以我们完全可以使用联合,`typeof`,或者其他类型: + +```ts twoslash +type Person = { age: number; name: string; alive: boolean }; +// ---cut--- +type I1 = Person["age" | "name"]; +// ^? + +type I2 = Person[keyof Person]; +// ^? + +type AliveOrName = "alive" | "name"; +type I3 = Person[AliveOrName]; +// ^? +``` + +试试看索引一个不存在的属性,你会发现报错: + +```ts twoslash +// @errors: 2339 +type Person = { age: number; name: string; alive: boolean }; +// ---cut--- +type I1 = Person["alve"]; +``` + +另一个示例是,使用 `number` 去索引任意类型,可以得到一个数组的元素。 +我们将其与 `typeof` 结合,能很方便地获取数组的元素类型 + +```ts twoslash +const MyArray = [ + { name: "Alice", age: 15 }, + { name: "Bob", age: 23 }, + { name: "Eve", age: 38 }, +]; + +type Person = typeof MyArray[number]; +// ^? +type Age = typeof MyArray[number]["age"]; +// ^? +// Or +type Age2 = Person["age"]; +// ^? +``` + +索引时只能使用类型,这意味着不能使用 `const` 进行变量引用: + +```ts twoslash +// @errors: 2538 2749 +type Person = { age: number; name: string; alive: boolean }; +// ---cut--- +const key = "age"; +type Age = Person[key]; +``` + +但是,您可以用类型别名进行类似的重构: + +```ts twoslash +type Person = { age: number; name: string; alive: boolean }; +// ---cut--- +type key = "age"; +type Age = Person[key]; +``` diff --git a/docs/documentation/zh/handbook-v2/Type Manipulation/Keyof Type Operator.md b/docs/documentation/zh/handbook-v2/Type Manipulation/Keyof Type Operator.md new file mode 100644 index 00000000..46304ca7 --- /dev/null +++ b/docs/documentation/zh/handbook-v2/Type Manipulation/Keyof Type Operator.md @@ -0,0 +1,32 @@ +--- +title: Keyof Type Operator +layout: docs +permalink: /zh/docs/handbook/2/keyof-types.html +oneline: "在类型上下文里使用 keyof 关键字" +--- + +## `keyof` 类型操作符 + +`keyof` 类型操作符接受一个对象类型,产出其 key 的字符或者数字的合集类型: + +```ts twoslash +type Point = { x: number; y: number }; +type P = keyof Point; +// ^? +``` + +如果这个类有一个 `string` 或者 `number` 索引签名,则 `keyof` 将返回这些类型: + +```ts twoslash +type Arrayish = { [n: number]: unknown }; +type A = keyof Arrayish; +// ^? + +type Mapish = { [k: string]: boolean }; +type M = keyof Mapish; +// ^? +``` + +注意,在本例中,`M` 是 `string | number` --这是因为 JavaScript 对象键总是强制为字符串,所以 `obj[0]` 总是与 `obj[“0”]` 相同。 + +`keyof` 类型特别适用于与映射类型组合,我们将在后面了解更多。 diff --git a/docs/documentation/zh/handbook-v2/Type Manipulation/Mapped Types.md b/docs/documentation/zh/handbook-v2/Type Manipulation/Mapped Types.md new file mode 100644 index 00000000..52f7f317 --- /dev/null +++ b/docs/documentation/zh/handbook-v2/Type Manipulation/Mapped Types.md @@ -0,0 +1,147 @@ +--- +title: Mapped Types +layout: docs +permalink: /zh/docs/handbook/2/mapped-types.html +oneline: "通过复用已有类型来生成类型。" +--- + +当你不想重复你自己时,有时一个类型需要基于另一个类型。 + +映射类型基于索引签名的语法构建,它用于声明尚未提前声明的属性类型: + +```ts twoslash +type Horse = {}; +// ---cut--- +type OnlyBoolsAndHorses = { + [key: string]: boolean | Horse; +}; + +const conforms: OnlyBoolsAndHorses = { + del: true, + rodney: false, +}; +``` + +映射类型是一种泛型类型,它使用 `PropertyKey` 的联合(通常 [通过 `keyof`](/zh/docs/handbook/2/indexed-access-types.html))来迭代键以创建类型: + +```ts twoslash +type OptionsFlags = { + [Property in keyof Type]: boolean; +}; +``` + +在此示例中,`OptionsFlags` 接收 `Type` 类型上所有的属性,并将它们的值转为 boolean。 + +```ts twoslash +type OptionsFlags = { + [Property in keyof Type]: boolean; +}; +// ---cut--- +type FeatureFlags = { + darkMode: () => void; + newUserProfile: () => void; +}; + +type FeatureOptions = OptionsFlags; +// ^? +``` + +### 映射修饰符 + +有两个附加修饰符可以在映射是应用到:`readonly` 和 `?` 分别作用于可变性和可选择性。 + +您可以通过前缀 `-` 来删除这些修饰符,或者用 `+` 增加它们。如果没有前缀,就默认为 `+`。 + +```ts twoslash +// 从类型的属性中移除 'readonly' 标记 +type CreateMutable = { + -readonly [Property in keyof Type]: Type[Property]; +}; + +type LockedAccount = { + readonly id: string; + readonly name: string; +}; + +type UnlockedAccount = CreateMutable; +// ^? +``` + +```ts twoslash +// 从类型的属性中移除 'optional' 标记 +type Concrete = { + [Property in keyof Type]-?: Type[Property]; +}; + +type MaybeUser = { + id: string; + name?: string; + age?: number; +}; + +type User = Concrete; +// ^? +``` + +## 通过 `as` 重新映射键 + +在 TypeScript 4.1 及更高版本,您可以使用映射类型中的 `as` 子句重新映射映射类型中的键: + +```ts +type MappedTypeWithNewProperties = { + [Properties in keyof Type as NewKeyType]: Type[Properties] +} +``` + + +您可以利用 [模版字面量类型](/docs/handbook/2/template-literal-types.html) 等功能,从以前的属性名称创建新的属性名称: + +```ts twoslash +type Getters = { + [Property in keyof Type as `get${Capitalize}`]: () => Type[Property] +}; + +interface Person { + name: string; + age: number; + location: string; +} + +type LazyPerson = Getters; +// ^? +``` + +您可以通过条件类型生成 `never` 来筛选出键: + +```ts twoslash +// 移除 'kind' 属性 +type RemoveKindField = { + [Property in keyof Type as Exclude]: Type[Property] +}; + +interface Circle { + kind: "circle"; + radius: number; +} + +type KindlessCircle = RemoveKindField; +// ^? +``` + +### 未来探索 + +映射类型与此类型操作部分中的其他功能配合得很好,例如,这里有 [使用条件类型的映射类型](/zh/docs/handbook/2/conditional-types.html) 它返回 `true` 或 `false`,这取决于对象是否将属性 `pii` 设置为文字 `true`。 + +```ts twoslash +type ExtractPII = { + [Property in keyof Type]: Type[Property] extends { pii: true } ? true : false; +}; + +type DBFields = { + id: { format: "incrementing" }; + name: { type: string; pii: true }; +}; + +type ObjectsNeedingGDPRDeletion = ExtractPII; +// ^? +``` diff --git a/docs/documentation/zh/handbook-v2/Type Manipulation/Template Literal Types.md b/docs/documentation/zh/handbook-v2/Type Manipulation/Template Literal Types.md new file mode 100644 index 00000000..2f695035 --- /dev/null +++ b/docs/documentation/zh/handbook-v2/Type Manipulation/Template Literal Types.md @@ -0,0 +1,218 @@ +--- +title: Template Literal Types +layout: docs +permalink: /zh/docs/handbook/2/template-literal-types.html +oneline: "通过摸板文本字符串生成变更属性的映射类型。" +--- + + +模板文本类型基于 [字符串文本类型](/docs/handbook/2/everyday-types.html#literal-types) 创建,并且能够通过联合来扩展多个字符串。 + +它们和 [JavaScript 中的模版字面量字符串](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) 的语法是一样的,只是用在了做类型。 +当使用具体的文本类型,模板文本通过连接内容生成新的字符串文本类型。 + +```ts twoslash +type World = "world"; + +type Greeting = `hello ${World}`; +// ^? +``` + +当在插值位置使用联合时,类型就是是各个联合成员表示的每个可能字符串文字的集合: + +```ts twoslash +type EmailLocaleIDs = "welcome_email" | "email_heading"; +type FooterLocaleIDs = "footer_title" | "footer_sendoff"; + +type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`; +// ^? +``` + +对于模板文字中的每个插值位置,并集将交叉相乘: + +```ts twoslash +type EmailLocaleIDs = "welcome_email" | "email_heading"; +type FooterLocaleIDs = "footer_title" | "footer_sendoff"; +// ---cut--- +type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`; +type Lang = "en" | "ja" | "pt"; + +type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`; +// ^? +``` + +我们一般建议人们使用提前生成的大型字符串联合,但这在较小的情况下很有用。 + +### 类型中的字符串联合 + +当要基于现有字符串来定义新字符串时,模版文本就大展神威了。 + +比如,JavaScript 中的一个常见模式是基于一个对象上现有的字段做扩展。我们要为一个函数提供类型定义,其增加了对 `on` 函数的支持,该函数可以让您知道何时发生了变化: + +```ts twoslash +// @noErrors +declare function makeWatchedObject(obj: any): any; +// ---cut--- +const person = makeWatchedObject({ + firstName: "Saoirse", + lastName: "Ronan", + age: 26, +}); + +person.on("firstNameChanged", (newValue) => { + console.log(`firstName was changed to ${newValue}!`); +}); +``` + +我们注意到 `on` 监听的是 `firstNameChanged` 事件,而不是 `firstName`,类型系统中模版文本就为处理这种字符串操作铺好了路。 + +```ts twoslash +type PropEventSource = { + on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void; +}; + +/// 用 'on' 方法创建一个 "watched object" +/// 这样您就可以监听属性的变化了 +declare function makeWatchedObject(obj: Type): Type & PropEventSource; +``` + +有了它,我们可以以给定的错误属性,来创建一些错误内容: + +```ts twoslash +// @errors: 2345 +type PropEventSource = { + on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void; +}; + +declare function makeWatchedObject(obj: T): T & PropEventSource; +// ---cut--- +const person = makeWatchedObject({ + firstName: "Saoirse", + lastName: "Ronan", + age: 26 +}); + +person.on("firstNameChanged", () => {}); + +// 可以防止打错字 +person.on("firstName", () => {}); + +person.on("frstNameChanged", () => {}); +``` + +### 模版文本的推断 + +请注意,最后的示例中没有重复使用原始值的类型。回调函数使用了 `any`。模版文本类型可以从替换位置进行类型推断。 + +我们可以将最后示例设置为泛型,从 `eventName` 字符串的部分内容推断,计算出关联的属性。 + +```ts twoslash +type PropEventSource = { + on + (eventName: `${Key}Changed`, callback: (newValue: Type[Key]) => void ): void; +}; + +declare function makeWatchedObject(obj: Type): Type & PropEventSource; + +const person = makeWatchedObject({ + firstName: "Saoirse", + lastName: "Ronan", + age: 26 +}); + +person.on("firstNameChanged", newName => { + // ^? + console.log(`new name is ${newName.toUpperCase()}`); +}); + +person.on("ageChanged", newAge => { + // ^? + if (newAge < 0) { + console.warn("warning! negative age"); + } +}) +``` + +这里我们把 `on` 变成了泛型方法。 + +当用户通过字符串 `"firstNameChange"` 调用的时候,TypeScript 会尝试推断 `Key` 的正确类型。 +通过这样,它会去匹配在 `"Changed"` 前面的内容作为 `Key`,并且推断其为字符串 `"firstName"`。 + +当 TypeScript 计算出这一点,`on` 方法可以得到原始对象上 `firstName` 的类型,在这里它是一个 `string` 类型。 + +同样的,当使用 `"ageChanged"` 进行调用的使用,TypeScript 会发现 `number` 类型的 `age`。 + +可以用不同方式进行推断,一般都是分解字符串,然后用不同的方式再组装起来。 + +## 内置字符串操作类型 + +为了辅助字符串操作,TypeScript 有一套用在字符串操作的类型集合。出于性能考虑,这些类型内置在编译器,在 TypeScript 附带的 `.d.ts` 中找不到。 +### `Uppercase` + +将字符串中的每个字符转为大写。 + +##### 示例 + +```ts twoslash +type Greeting = "Hello, world" +type ShoutyGreeting = Uppercase +// ^? + +type ASCIICacheKey = `ID-${Uppercase}` +type MainID = ASCIICacheKey<"my_app"> +// ^? +``` + +### `Lowercase` + +将字符串中的每个字符转为小写。 + +##### 示例 + +```ts twoslash +type Greeting = "Hello, world" +type QuietGreeting = Lowercase +// ^? + +type ASCIICacheKey = `id-${Lowercase}` +type MainID = ASCIICacheKey<"MY_APP"> +// ^? +``` + +### `Capitalize` + +将字符串中的首个字符转为大写。 +##### Example + +```ts twoslash +type LowercaseGreeting = "hello, world"; +type Greeting = Capitalize; +// ^? +``` + +### `Uncapitalize` + +将字符串中的首个字符转为等效的小写字符 + +##### 示例 + +```ts twoslash +type UppercaseGreeting = "HELLO WORLD"; +type UncomfortableGreeting = Uncapitalize; +// ^? +``` + +
+ 内置字符操作类型的细节 +

从 TypeScript 4.1 开始,这些内在函数的代码直接使用 JavaScript 字符串运行时函数进行操作,并且不支持区域本地化设置。

+
+function applyStringMapping(symbol: Symbol, str: string) {
+    switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
+        case IntrinsicTypeKind.Uppercase: return str.toUpperCase();
+        case IntrinsicTypeKind.Lowercase: return str.toLowerCase();
+        case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1);
+        case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1);
+    }
+    return str;
+}
+
diff --git a/docs/documentation/zh/handbook-v2/Type Manipulation/Typeof Type Operator.md b/docs/documentation/zh/handbook-v2/Type Manipulation/Typeof Type Operator.md new file mode 100644 index 00000000..0cc7b274 --- /dev/null +++ b/docs/documentation/zh/handbook-v2/Type Manipulation/Typeof Type Operator.md @@ -0,0 +1,71 @@ +--- +title: Typeof Type Operator +layout: docs +permalink: /zh/docs/handbook/2/typeof-types.html +oneline: "在类型上下文中使用 typeof 操作符" +--- + +## `typeof` 类型操作符 + +在 JavaScript 中,已经有可以应用在 _表达式_ 上下文的 `typeof` 操作符。 + +```ts twoslash +// Prints "string" +console.log(typeof "Hello world"); +``` + +TypeScript 新增了可以用在 _类型_ 上下文的 `typeof`,用以推断一个变量或者属性的 _类型_。 + +```ts twoslash +let s = "hello"; +let n: typeof s; +// ^? +``` + +对于基础类型来说这不是很实用,不过与其它类型操作符结合,您可以用 `typeof` 很方便地表示大量模式。 +例如,让我们先看看这个预定义的 `ReturnType` 类型。 +其接收一个 _函数类型_ 并且产出它的返回值类型: + +```ts twoslash +type Predicate = (x: unknown) => boolean; +type K = ReturnType; +// ^? +``` + +如果我们想给 `ReturnType` 传一个函数名称,我们会看到一个指示性错误: + +```ts twoslash +// @errors: 2749 +function f() { + return { x: 10, y: 3 }; +} +type P = ReturnType; +``` + +请记住 _值_ 和 _类型_ 不是一回事。 +要推断 `f` 这个 _值_ 的 _类型_,我们需要用到 `typeof`: + +```ts twoslash +function f() { + return { x: 10, y: 3 }; +} +type P = ReturnType; +// ^? +``` + +### 局限性 + +TypeScript 有意限制您所能够用 `typeof` 表达式的种类。 + +特别是,在标识符(即变量名)或其属性上使用 `typeof` 是唯一合法的。 + +这有助于规避您写一些,您认为执行了,但是却没有执行的代码这样的迷惑陷阱。 + +```ts twoslash +// @errors: 1005 +declare const msgbox: () => boolean; +// type msgbox = any; +// ---cut--- +// 打算使用 = ReturnType +let shouldContinue: typeof msgbox("Are you sure you want to continue?"); +``` diff --git a/docs/documentation/zh/handbook-v2/Type Manipulation/_Creating Types from Types.md b/docs/documentation/zh/handbook-v2/Type Manipulation/_Creating Types from Types.md new file mode 100644 index 00000000..bae7aad4 --- /dev/null +++ b/docs/documentation/zh/handbook-v2/Type Manipulation/_Creating Types from Types.md @@ -0,0 +1,22 @@ +--- +title: Creating Types from Types +layout: docs +permalink: /zh/docs/handbook/2/types-from-types.html +oneline: "通过现有类型创建更多类型的几种方式的概览。" +--- + +TypeScript 的类型系统非常强大,因为它允许用其他类型表示类型。 + +这个说法的最简单的形式就是泛型,实际上我们有各种各样的类型运算符。 +也可以用我们已有的 _值_ 来表示类型。 + +通过组合各种类型的运算符,我们可以以简洁、可维护的方式表达复杂的操作和值。 +在本节中,我们将介绍用现有类型或值表示新类型的方法。 + +- [泛型](/zh/docs/handbook/2/generics.html) - 接收参数的类型 +- [Keyof 类型运算符](/zh/docs/handbook/2/keyof-types.html) - 通过 `keyof` 运算符创建新类型 +- [Typeof 类型运算符](/zh/docs/handbook/2/typeof-types.html) - 通过 `typeof` 运算符创建新类型 +- [按索引访问类型](/zh/docs/handbook/2/indexed-access-types.html) - 通过 `Type['a']` 语法访问一个类型的子集 +- [条件类型](/zh/docs/handbook/2/conditional-types.html) - 类型系统中像 if 语句那样的类型 +- [映射类型](/zh/docs/handbook/2/mapped-types.html) - 通过已有类型的属性映射创建类型 +- [模版字面量类型](/zh/docs/handbook/2/template-literal-types.html) - 已映射类型,其通过模版字面量字符串,改变其属性 diff --git a/docs/documentation/zh/javascript/Creating DTS files From JS.md b/docs/documentation/zh/javascript/Creating DTS files From JS.md new file mode 100644 index 00000000..5f94ffa9 --- /dev/null +++ b/docs/documentation/zh/javascript/Creating DTS files From JS.md @@ -0,0 +1,90 @@ +--- +title: Creating .d.ts Files from .js files +layout: docs +permalink: /zh/docs/handbook/declaration-files/dts-from-js.html +oneline: "如何生成并添加 .d.ts 到 JavaScript 项目" +translatable: true +--- + +[使用 TypeScript 3.7](/docs/handbook/release-notes/typescript-3-7.html#--declaration-and---allowjs), +TypeScript 新增了对使用 JSDoc 语法的 JavaScript,生成 .d.ts 文件的支持。 + +这种设置意味着,您可以拥有 TypeScript 支持的编辑器的编辑器体验,而无需将项目移植到TypeScript,也无需在代码库中维护.d.ts文件。 + +TypeScript 支持绝大多数的 JSDoc 标签,您可以参考 [这里的手册](/docs/handbook/type-checking-javascript-files.html#supported-jsdoc)。 + +## 配置您的项目去输出 .d.ts 文件 + +要在项目中添加 .d.ts 文件的构建,最多需要执行四个步骤: + +- 添加 TypeScript 到您的开发依赖 +- 添加一个 `tsconfig.json` 来配置 TypeScript +- 运行 TypeScript 编译器来生成 JS 文件的 d.ts 文件 +- (可选) 编辑 package.json 来指定类型文件 + +### 添加 TypeScript + +您可以在我们的 [安装页面](/download) 中学会如何添加。 + +### TSConfig +TSConfig 是一份 jsonc 文件,其中配置了您的编译器标记,已经申明从哪里查找文件。 +在本例中,您将需要一个如下所示的文件: + +```jsonc tsconfig +{ + // Change this to match your project + "include": ["src/**/*"], + + "compilerOptions": { + // Tells TypeScript to read JS files, as + // normally they are ignored as source files + "allowJs": true, + // Generate d.ts files + "declaration": true, + // This compiler run should + // only output d.ts files + "emitDeclarationOnly": true, + // Types should go into this directory. + // Removing this would place the .d.ts files + // next to the .js files + "outDir": "dist" + } +} +``` + +您可以在 [tsconfig 参考](/tsconfig) 学习更多配置。 +另一个使用 TSConfig 的选择既是 CLI,它与 CLI 指令的行为一样。 + +```sh +npx -p typescript tsc src/**/*.js --declaration --allowJs --emitDeclarationOnly --outDir types +``` + +## 运行编译器 + +您可以在我们的 [安装页面](/download) 中学会如何操作。 + + +You want to make sure these files are included in your package if you have the files in your project's `.gitignore`. + +## 编辑 package.json + +TypeScript 复制了 `package.json` 中模块的节点解析,另外还有一个查找 `.d.ts` 文件的步骤。 +大致上,解析将从一个可选的 `"types"` 开始检查,之后是 `"main"` 字段,之后尝试查找项目根目录下的 `index.d.ts` 。 + +| Package.json | .d.ts 的默认位置 | +| :------------------------ | :----------------------------- | +| 无 "types" 字段 | 检查 "main", 然后是 index.d.ts | +| "types": "main.d.ts" | main.d.ts | +| "types": "./dist/main.js" | ./dist/main.d.ts | + +如果缺失, 就找 "main" 字段 + +| Package.json | .d.ts 的默认位置 | +| :----------------------- | :------------------------ | +| 无 "main" 字段 | index.d.ts | +| "main":"index.js" | index.d.ts | +| "main":"./dist/index.js" | ./dist/index.d.ts | + +## Tips + +如果您想为您的 .d.ts 文件编写测试,试试这个 [tsd](https://github.com/SamVerschueren/tsd). diff --git a/docs/documentation/zh/javascript/Intro to JS with TS.md b/docs/documentation/zh/javascript/Intro to JS with TS.md new file mode 100644 index 00000000..e5ad58c0 --- /dev/null +++ b/docs/documentation/zh/javascript/Intro to JS with TS.md @@ -0,0 +1,69 @@ +--- +title: JS Projects Utilizing TypeScript +layout: docs +permalink: /zh/docs/handbook/intro-to-js-ts.html +oneline: 如何使用 TypeScript 给 JavaScript 文件添加类型检查 +translatable: true +--- + +在不同代码库中,TypeScript 的类型系统有不同级别的严格性: +- 仅基于 JavaScript 代码推断的类型系统 +- 在 JavaScript 中 [通过 JSDoc](/docs/handbook/jsdoc-supported-types.html) 增加类型 +- 在 JavaScript 文件中使用 `// @ts-check` +- TypeScript 代码 +- TypeScript 代码,其 [`strict`](/tsconfig#strict) 设置为开启 + +每一步都代表着向更安全的类型系统的迈进,但并非每个项目都需要这种级别的验证。 +## 使用 JavaScript 的 TypeScript + +就是当你使用的一个编辑器,它使用 TypeScript 来提供如自动补全,标识跳转的工具,还有像重命名这样的重构工具。 +在 [首页](/) 有一个自带 TypeScript 插件的编辑器清单。 + +## 在 JS 中通过 JSDoc 提供类型提示 + +在一个 `.js` 文件中,类型时通常是可以被推断的。当类型不能被推断时,他们也可以使用 JSDoc 语法加以指定。 + +JSDoc 注释出现在声明之前,将用于设置该声明的类型。例如: + +```js twoslash +/** @type {number} */ +var x; + +x = 0; // OK +x = false; // OK?! +``` + +您可以在 [受 JSDoc 支持的类型](/docs/handbook/jsdoc-supported-types.html) 中找到 JSDoc 所支持的模式的完整清单。 + +## `@ts-check` + +上面代码示例中的最后一行,在 TypeScript 中会引发报错,不过默认在 JS 项目中却不会。 +要使其在您的 JavaScript 中也报错,请添加: `// @ts-check` 到您的 `.js` 文件的第一行,让 TypeScript 去触发该错误。 + + +```js twoslash +// @ts-check +// @errors: 2322 +/** @type {number} */ +var x; + +x = 0; // OK +x = false; // Not OK +``` + +如果您有大量的 JavaScript 文件,您若想添加错误提示,那么您可以转为使用 [`jsconfig.json`](/docs/handbook/tsconfig-json.html)。 +您可以通过给文件添加 `// @ts-nocheck` 注释以跳过个别文件的检查。 + +TypeScript 有时会有意料之外的报错,这种情形下您可以通过在上一行添加 `// @ts-ignore` 或者 `// @ts-expect-error` 来忽略这些报错。 + +```js twoslash +// @ts-check +/** @type {number} */ +var x; + +x = 0; // OK +// @ts-expect-error +x = false; // Not OK +``` + +要学习有关 JavaScript 如何被 TypeScript 解释,请参阅 [TS 类型如何检查 JS](/docs/handbook/type-checking-javascript-files.html) diff --git a/docs/documentation/zh/javascript/Type Checking JavaScript Files.md b/docs/documentation/zh/javascript/Type Checking JavaScript Files.md new file mode 100644 index 00000000..95257937 --- /dev/null +++ b/docs/documentation/zh/javascript/Type Checking JavaScript Files.md @@ -0,0 +1,308 @@ +--- +title: Type Checking JavaScript Files +layout: docs +permalink: /zh/docs/handbook/type-checking-javascript-files.html +oneline: 如何使用 TypeScript 给 JavaScript 文件添加类型检查 +--- + +与 `.ts` 文件相比较,`.js` 文件的检查机制有一些明显的区别。 + +## 属性是从类主体中的赋值推断出来的 + +ES2015 没有方法在类里面声明其属性。属性是动态赋值的,就像对象字面量。 + +在 `.js` 文件中,编译器从类主体中的属性赋值推断属性。 +属性的类型就是构造函数中所赋予的类型,除非在那里没有指定,或者在构造函数中指定的是 undefined 或者 null。 +因此,类型是这些赋值中所有右侧值的类型的并集。 +在构造函数中被定义的属性总是被设定为存在,而那些定义在方法、getters 或者 setters 中的就被看作是可选的。 + +```js twoslash +// @checkJs +// @errors: 2322 +class C { + constructor() { + this.constructorOnly = 0; + this.constructorUnknown = undefined; + } + method() { + this.constructorOnly = false; + this.constructorUnknown = "plunkbat"; // 没问题,constructorUnknown 的类型是 string | undefined + this.methodOnly = "ok"; // 没问题,但是 methodOnly 的类型依然是 undefined + } + method2() { + this.methodOnly = true; // 也没有问题, methodOnly 的类型是 string | boolean | undefined + } +} +``` + +如果属性从来没有在类主体中设置过,那他们被认定为 unknown。 +如果您的类中具有只能读取的属性,请在构造函数中添加声明,然后使用 JSDoc 对其进行注释,以指定类型。 +如果以后才会对其进行初始化,您甚至不必给出值: + +```js twoslash +// @checkJs +// @errors: 2322 +class C { + constructor() { + /** @type {number | undefined} */ + this.prop = undefined; + /** @type {number | undefined} */ + this.count; + } +} + +let c = new C(); +c.prop = 0; // OK +c.count = "string"; +``` + +## 构造函数与类是等同的 + +在 ES2015 之前,JavaScript 是使用构造器函数而非类。 +编译器支持这种模式,且将构造器函数理解为等同于 ES2015 的类。 +属性的推断规则与上述的方式完全相同。 + +```js twoslash +// @checkJs +// @errors: 2683 2322 +function C() { + this.constructorOnly = 0; + this.constructorUnknown = undefined; +} +C.prototype.method = function () { + this.constructorOnly = false; + this.constructorUnknown = "plunkbat"; // OK, the type is string | undefined +}; +``` + +## 支持 CommonJS 模块 + +在一个 `.js` 文件中,TypeScript 能理解 CommonJS 模块格式。 +`exports` 与 `module.exports` 的赋值被认定为 export 声明。 +类似的, `require` 方法的调用,被视为模块引入。例如: + +```js +// 等同 `import module "fs"` +const fs = require("fs"); + +// 等同 `export function readFile` +module.exports.readFile = function (f) { + return fs.readFileSync(f); +}; +``` + +在语法上,Javascript 中的模块支持,比 TypeScript 的更具宽松。 +其支持大多数赋值和声明的组合。 +## 类型,函数和对象字面量都是命名空间 + +在 `.js` 文件中,类型是命名空间。 +它可以用来嵌套类型,比如: + +```js twoslash +class C {} +C.D = class {}; +``` + +并且,对于 ES2015 之前的代码,它可以用来模仿静态方法: + +```js twoslash +function Outer() { + this.y = 2; +} + +Outer.Inner = function () { + this.yy = 2; +}; + +Outer.Inner(); +``` + +它也可以用来创建一个简单的命名空间: + +```js twoslash +var ns = {}; +ns.C = class {}; +ns.func = function () {}; + +ns; +``` + +也支持其它的变体: + +```js twoslash +// IIFE +var ns = (function (n) { + return n || {}; +})(); +ns.CONST = 1; + +// defaulting to global +var assign = + assign || + function () { + // code goes here + }; +assign.extra = 1; +``` + +## 对象字面量是开放的 + +一个 `.ts` 文件中,一个初始化了变量声明的对象字面量,即赋予了其声明的类型。 +不允许加入原始字面量中所未指定的新成员。 +该规则在 `.js` 文件中变为宽松了;对象字面量有这开放的类型(索引签名),其允许添加和查看最初没有被定义的属性。 +比如: + +```js twoslash +var obj = { a: 1 }; +obj.b = 2; // Allowed +``` + +对象字面量的这一行为就好像其有一个索引签名 `[x:string]: any`,这样使其作为开放的 map 对待,而非封闭对象。 +就像其他特殊的 JS 检查行为,这些行为可以通过为其变量指定一个 JSDoc 类型而改变。比如: + +```js twoslash +// @checkJs +// @errors: 2339 +/** @type {{a: number}} */ +var obj = { a: 1 }; +obj.b = 2; +``` + +## null、undefined 和空数组初始化为 any 或者 any[] 类型 + +任何变量、参数或者属性,一旦被初始化为 `null` 或者 `undefined`,其类型就会为 any,即使开启了严格的 `null` 检查。 +任何变量、参数或者属性,一旦被初始化为 `[]` ,其类型将为 `any[]`,即使开启了严格的 `null` 检查。 +唯一的例外是如上所述具有多个初始值设定项的属性。 + +```js twoslash +function Foo(i = null) { + if (!i) i = 1; + var j = undefined; + j = 2; + this.l = []; +} + +var foo = new Foo(); +foo.l.push(foo.i); +foo.l.push("end"); +``` + +## 函数的参数默认是可选的 + +由于在 ES2015 之前的 JavaScript中,没有办法制定参数的可选性,所以 `.js` 所有的方法的参数都被视为可选的。 +调用时传递的参数少于所声明的个数,是被允许的。 + +需要注意的是,调用时传递过多的参数是错误的。 + +比如: + +```js twoslash +// @checkJs +// @strict: false +// @errors: 7006 7006 2554 +function bar(a, b) { + console.log(a + " " + b); +} + +bar(1); // 没问题,第二个参数被视为可选 +bar(1, 2); +bar(1, 2, 3); // 报错了,传递过多的参数 +``` + +JSDoc 注释的函数不在此规则之中。 +使用 JSDoc 可选参数语法(`[` `]`)来表示可选性。比如: + +```js twoslash +/** + * @param {string} [somebody] - Somebody's name. + */ +function sayHello(somebody) { + if (!somebody) { + somebody = "John Doe"; + } + console.log("Hello " + somebody); +} + +sayHello(); +``` + +## 使用了 `arguments` 的 Var-args 参数声明推断 + +如果一个函数体引用了 `arguments` 引用,则隐式认为该函数具有 var-arg 参数(就像`(...arg: any[]) => any`)。使用 JSDoc 的 var-arg 语法来指定参数的类型。 + +```js twoslash +/** @param {...number} args */ +function sum(/* numbers */) { + var total = 0; + for (var i = 0; i < arguments.length; i++) { + total += arguments[i]; + } + return total; +} +``` + +## 未指定类型的参数被视为 `any` 类型 + +由于没有在 JavaScript 中没有指定泛型类型参数的自然语法,所以为制定类型的参数默认为 `any`。 + +### 在 extends 子句中 + +举个例子, `React.Component` 被定义为有两个参数,为 `Props` 和 `State`。 +在 `.js` 文件中,没有合理的方式,在扩展子句中去指定它们,于是这两个参数就为 `any`: + +```js +import { Component } from "react"; + +class MyComponent extends Component { + render() { + this.props.b; // Allowed, since this.props is of type any + } +} +``` + +使用 `@augments` 来明确指定类型。比如: + +```js +import { Component } from "react"; + +/** + * @augments {Component<{a: number}, State>} + */ +class MyComponent extends Component { + render() { + this.props.b; // Error: b does not exist on {a:number} + } +} +``` + +### 在JSDoc 参照中 + +JSDoc 中未指定类型的参数默认为 any: + +```js twoslash +/** @type{Array} */ +var x = []; + +x.push(1); // 没问题 +x.push("string"); // 没问题, x 的类型为 Array + +/** @type{Array.} */ +var y = []; + +y.push(1); // 没问题 +y.push("string"); // 报错, string 不能飞配给 number 类型 +``` + +### 在函数调用中 + +泛型函数的调用,使用泛型参数来推断类型参数。有的时候这一过程无法推断出任何类型,主要是因为缺乏推断来源;因此,类型参数将默认设为 `any`。比如: + +```js +var p = new Promise((resolve, reject) => { + reject(); +}); + +p; // Promise; +``` + +了解JSDoc中的所有可用功能,请参阅 [此手册](/docs/handbook/jsdoc-supported-types.html)。 diff --git a/docs/documentation/zh/tutorials/Angular.md b/docs/documentation/zh/tutorials/Angular.md new file mode 100644 index 00000000..c45fff80 --- /dev/null +++ b/docs/documentation/zh/tutorials/Angular.md @@ -0,0 +1,13 @@ +--- +title: Angular +layout: docs +permalink: /zh/docs/handbook/angular.html +oneline: 在 Angular 中使用 TypeScript +deprecated: true +--- + +Angular 是一个完全用 TypeScript 构建的现代框架,因此,将 TypeScript 与 Angular 结合使用可以提供无缝体验。 + +Angular 文档支持将 TypeScript 作为一级公民,而且将其作为主要语言。考虑到这一点,[Angular的网站](https://angular.io) 将始终是使用 Angular 和 TypeScript 的最新参考。 + +查看 [快速入门指引]](https://angular.io/docs/ts/latest/quickstart.html) 开始学习 Angular 吧!