From 83e6bf0ac85958b23eb6d2a57a81653bbe9467c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=80=E9=9A=90?= Date: Tue, 26 Mar 2024 11:35:42 +0800 Subject: [PATCH] =?UTF-8?q?docs(=E4=BF=AE=E6=94=B9=E7=AC=94=E8=AE=B0):=20?= =?UTF-8?q?=E4=BF=AE=E8=AE=A2typescript=E7=AC=94=E8=AE=B0=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 79 -- ...37\347\237\245\350\257\206\347\202\271.md" | 688 ++++++++++++++---- ...37\347\237\245\350\257\206\347\202\271.md" | 184 +++-- 3 files changed, 658 insertions(+), 293 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aca15e3..8b13789 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,80 +1 @@ -# Changelog - -All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. - -## [3.0.0](https://github.com/simply-none/latest-blogs/compare/v5.1.14...v3.0.0) (2023-06-09) - - -### ⚠ BREAKING CHANGES - -* **change scope:** break desc -* **jousin:** jousin - -### Features - -* **accounts:** jk ([9d5c21a](https://github.com/simply-none/latest-blogs/commit/9d5c21a64bef921b619f8992114fbe57bef3f5c8)) -* **accounts:** o ([80fb5e1](https://github.com/simply-none/latest-blogs/commit/80fb5e1fc5071f122ab32da7486f03af04dad9f4)) -* d ([c83f7e8](https://github.com/simply-none/latest-blogs/commit/c83f7e84b234662cb200ce00684ff7fa4f6420a0)) -* **scope:** subject ([b1e46de](https://github.com/simply-none/latest-blogs/commit/b1e46de84e7141943d2fc214211362be9241e7ad)) - -### Bug Fixes - -* **笔记:** 更新配置文件 ([1a0f75b](https://github.com/simply-none/latest-blogs/commit/1a0f75ba31ba70fc0775536e70d9291cd72cf53d)) -* **配置文件:** 配置文件修改 ([24ebc64](https://github.com/simply-none/latest-blogs/commit/24ebc641068948d812455cbe760dc60c341df8b6)) -* **accounts:** 删除文件 ([0d08064](https://github.com/simply-none/latest-blogs/commit/0d08064bb632e200f45a66c01bb911b4aa08b797)) -* **change scope:** shore desc ([5768042](https://github.com/simply-none/latest-blogs/commit/576804226a556a913c800bb79a3ff8b914c60386)), closes [#322](https://github.com/simply-none/latest-blogs/issues/322) -* **custom:** 主题 ([49a71e2](https://github.com/simply-none/latest-blogs/commit/49a71e2075e863c677d2184ea6e400d4a38efa87)) -* **d:** d ([5c12ed5](https://github.com/simply-none/latest-blogs/commit/5c12ed550486af85637f916ab7adb369bf4e9f58)) - - -### Features - -* **accounts:** jk ([9d5c21a](https://github.com/simply-none/latest-blogs/commit/9d5c21a64bef921b619f8992114fbe57bef3f5c8)) -* d ([c83f7e8](https://github.com/simply-none/latest-blogs/commit/c83f7e84b234662cb200ce00684ff7fa4f6420a0)) -* **scope:** subject ([b1e46de](https://github.com/simply-none/latest-blogs/commit/b1e46de84e7141943d2fc214211362be9241e7ad)) - - -### Performance Improvements - -* **admin:** 忽略项目测试文件 ([598029b](https://github.com/simply-none/latest-blogs/commit/598029b09eb5205391e6469a7e61bb72682d5b11)) - - -### Tests - -* **jousin:** jousin ([3745c87](https://github.com/simply-none/latest-blogs/commit/3745c8768129e86aed2291a89d7f5955e513fa37)), closes [#23](https://github.com/simply-none/latest-blogs/issues/23) - - -### Build System - -* **rw:** rqw ([ebc23ef](https://github.com/simply-none/latest-blogs/commit/ebc23efd41993c3a8aa9eafa08f36ca5568b06a2)) - - -### Docs - -* **accounts:** 变更 ([cee6a3e](https://github.com/simply-none/latest-blogs/commit/cee6a3e6ada006b45009c4c2e2ecd446fbccc965)) -* **accounts:** test ([722c4a8](https://github.com/simply-none/latest-blogs/commit/722c4a88e5219fbbc2dd1470073d2c3ed93bcb7c)) -* **admin:** none ([64b21a1](https://github.com/simply-none/latest-blogs/commit/64b21a1ed4ea1f46333148e14a12843b3da7e823)) - -### BREAKING CHANGES - -* **change scope:** break desc -* **jousin:** jousin - - - -## [5.1.14](https://github.com/simply-none/latest-blogs/compare/v5.1.13...v5.1.14) (2023-04-20) - - - -## [5.1.13](https://github.com/simply-none/latest-blogs/compare/v5.1.7...v5.1.13) (2023-04-20) - - - -## [5.1.7](https://github.com/simply-none/latest-blogs/compare/v0.0.3...v5.1.7) (2023-04-20) - - - -## 0.0.3 (2023-03-27) - - diff --git "a/docs/usage-frame/typescript/typescript\344\270\200\346\234\237\347\237\245\350\257\206\347\202\271.md" "b/docs/usage-frame/typescript/typescript\344\270\200\346\234\237\347\237\245\350\257\206\347\202\271.md" index aff2a88..73ee3a4 100644 --- "a/docs/usage-frame/typescript/typescript\344\270\200\346\234\237\347\237\245\350\257\206\347\202\271.md" +++ "b/docs/usage-frame/typescript/typescript\344\270\200\346\234\237\347\237\245\350\257\206\347\202\271.md" @@ -1,28 +1,29 @@ # TypeScript 知识点(第一期) > 参考文档: -> http://www.patrickzhong.com/TypeScript/ -> https://juejin.cn/post/7018805943710253086 -> https://juejin.cn/post/7058868160706904078 -> 注意:可能有些过时内容 +> http://www.patrickzhong.com/TypeScript/ +> https://juejin.cn/post/7018805943710253086 +> https://juejin.cn/post/7058868160706904078 +> 注意:可能有些过时内容 > 😢😢😢表示文中不明白的,未记录的内容 - -> **注意事项** -> ❌:表示实际上和记录有误或不一致的内容 -> 🟢:表示不太熟悉且重要的内容 -> 😢:表示尚未搞懂的内容 +> **注意事项** +> ❌:表示实际上和记录有误或不一致的内容 +> 🟢:表示不太熟悉且重要的内容 +> 😢:表示尚未搞懂的内容 > 🛑:阅读中断标志 ## 变量声明 var、let、const比较: + - var声明可以在包含他的函数、模块、命名空间、全局作用域中的任何位置被访问到,可以多次使用var声明同一个变量,属于函数作用域或var作用域 - let声明:只能在包含他们的块内访问(比如大括号括起的,又比如同一个文件内),声明之前不能被读写(暂时性死区),只能声明一次(又,不能对函数参数使用let重新声明,除非在函数内一个明显的块内(用大括号括起的)),属于词法作用域或块作用域 - const声明:赋值后不能再改变,拥有与let相同的作用域规则 ::: code-group -```typescript [var变量] + +```typescript // 此处由于setTimeout是微任务,在所有宏任务执行完毕后再执行,此时i为10 // 然后执行微任务,由于i是一个全局变量,所以每一条语句的i的值都为10 for (var i = 0; i < 10; i++) { @@ -37,7 +38,8 @@ for (var i = 0; i < 10; i++) { } ``` -```typescript [let变量] + +```typescript // 报错 function f(x: number) { let x = 100 @@ -50,6 +52,7 @@ function f(x: number) { } ``` + ::: ## 解构 @@ -67,24 +70,27 @@ console.log(first, second) ## 基础知识 -1. 脚本编译 ts 文件,使用命令`tsc xxx.ts xxx.js` -2. 初始化tsconfig文件,使用命令`tsc --init` -3. 使用创造函数 new 创建的对象`new Boolean()`,是对象类型`Boolean`,而非某些特定的原始类型`boolean` +1. 脚本编译 ts 文件,使用命令 `tsc xxx.ts xxx.js` +2. 初始化tsconfig文件,使用命令 `tsc --init` +3. 使用创造函数 new 创建的对象 `new Boolean()`,是对象类型 `Boolean`,而非某些特定的原始类型 `boolean` ### 声明文件 > 参考文档:https://zhuanlan.zhihu.com/p/542379032 定义: -- 声明文件以`.d.ts`结尾 + +- 声明文件以 `.d.ts`结尾 要点: + - ts编译器会根据tsconfig.json中的file、include、exclude三个字段去处理过滤后包含的所有的ts、tsx、d.ts文件,默认情况下是加载和tsconfig同级目录下的所有上述文件 - d.ts声明文件禁止定义具体的实现 - 当使用第三方库时,需引用对应的声明文件,才能获得对应的代码补全、接口提示等功能 ::: code-group -```typescript [声明全局变量] + +```typescript declare var Xxx; declare function () {} declare class {} @@ -94,7 +100,7 @@ interface A {} type A = {} ``` -```typescript [导入资源] +```typescript // 加载图片: // 定义图片声明:image.d.ts declare module '*.png' { @@ -107,31 +113,30 @@ import logo from './assets/logo.png' ::: - - - - - ## 基础类型概述 1. `unknown`类型 -定义:表示一个当前时刻还不知道类型的变量,可以将任何类型赋值给该类型,可以使用任意类型方法/属性(编译不报错)。 + 定义:表示一个当前时刻还不知道类型的变量,可以将任何类型赋值给该类型,可以使用任意类型方法/属性(编译不报错)。 使用: + - 若想缩小改类型的范围(类型收窄),可以使用条件语句+逻辑判断(typeof、比较符、类型检查、类型断言),之后就只能使用该范围内的类型方法/属性 注意: + - unknown只能赋值给unknown和any 2. `any`类型 - + 定义:表示一个当前时刻不清楚类型的变量,可以将任何类型赋值给该类型,可以使用任意类型方法/属性(编译不报错)。 使用: + - 对于不想进行类型检查的变量,可以标记为any类型 - 用于旧项目迁移到typescript项目 注意: + - 声明且没对变量赋值,若未指定类型,会被识别为 any 类型,但是不能调用该值没有的方法或属性。比如它的类型可能是undefined,故在赋值之前不能调用某些方法,比如toString。 ```typescript @@ -145,9 +150,11 @@ a.concat([]) 定义:表示没有任何类型,与any相反 场景: + - 当函数无返回值或显式返回undefined时,此时可以给函数返回值设置为void类型,而非undefined类型(只有显式返回undefined才可设置undefined) 赋值: + - null - undefined @@ -156,6 +163,7 @@ a.concat([]) 定义:表示它本身 使用: + - 是所有类型的子类型,可以赋值给任何类型的变量 - 指定了 **--strictNullChecks** 之后,只能赋值给any和它本身 @@ -164,44 +172,46 @@ a.concat([]) 定义:表示它本身 使用: + - 是所有类型的子类型,可以赋值给任何类型的变量 - 指定了 **--strictNullChecks** 之后,只能赋值给any、void和它本身 注意: -- 在指定了 **--strictNullChecks** 之后,*函数的可选参数*以及*类的可选属性*的类型会被自动的加上`| undefined` + +- 在指定了 **--strictNullChecks** 之后,*函数的可选参数*以及*类的可选属性*的类型会被自动的加上 `| undefined` 6. `never`类型 定义:表示永远不存在的值的类型 场景: + - 用于表示抛出异常的函数的函数返回值类型 - 用于无返回值(连undefined都没有的那种)的函数(表达式)的函数返回值类型,比如函数执行过程中,出现了死循环 使用: + - never类型是任何类型的子类型,可以赋值给任何类型的变量 - **只有never类型才能赋值给never类型** 7. `boolean`类型 - - - 8. `number`类型 - -9. `bigint`类型 +9. `bigint`类型 - 后缀以n结尾,环境必须是es2020+ 10. `string`类型 语法: + ```typescript const str: string = `这是一个模板字符串,当前时间:${new Date()}`; ``` -11. `array`类型 +11. `array`类型 数组类型的定义方式,如下: + ```typescript // 类型 + 方括号 let a: (number | string)[] = [1, "2"]; @@ -213,7 +223,8 @@ let c: { } = [1, "2"]; ``` -常用的类数组类型都有自己的接口定义,分别有`IArguments`, `NodeList`, `HTMLCollection`等,其中 +常用的类数组类型都有自己的接口定义,分别有 `IArguments`, `NodeList`, `HTMLCollection`等,其中 + ```typescript // IArguments的接口类型如下 interface IArguments { @@ -223,11 +234,12 @@ interface IArguments { } ``` -12. `tuple`类型 +12. `tuple`类型 定义:元组表示一个**已知数量和类型**的数组 表示形式如下: + ```typescript // 非具名元素,可选参数 const tuple: [string, number, boolean?] = ['1', 1, true] @@ -243,12 +255,14 @@ const [a, b, ...c] = tuple1 ``` **注意**: + - 解构元组时,超过元组定义时的索引范围(元组的总长度,包括可选的)会报错,若含剩余参数,则不会报错(值为undefined) -- 当无具名元素名称时,若可选,则在类型后面加上?,比如`boolean?`(这个只有所有的元素都是非具名的时候才行) +- 当无具名元素名称时,若可选,则在类型后面加上?,比如 `boolean?`(这个只有所有的元素都是非具名的时候才行) -13. `enum`类型 +13. `enum`类型 定义: + - 枚举类型表示可以有特殊名字的一组值 - 字面量枚举成员类型:指不带初始值的常量枚举成员、值被初始化为字符串字面量、数字字面量的成员。 - 当所有枚举成员都有字面量枚举值时,他们就成为了字面量枚举成员类型。而枚举类型也是枚举成员类型的联合类型 @@ -257,7 +271,7 @@ const [a, b, ...c] = tuple1 ::: code-group -```typescript [枚举类型] +```typescript // 定义 enum Color { Red = 1, Blue, Green } @@ -268,24 +282,27 @@ const c: Color = Color.Red const s: string = Color[1] ``` -```typescript [枚举成员类型] +```typescript enum Color { Red = 1, Blue, Green } type enumChildType = Color.Red +// enumChildType的类型是1,故而该类型的值,只能是1,不能是其他值 // Type 'Color.Blue' is not assignable to type 'Color.Red'. let a: enumChildType = Color.Blue -// 而下面的就没错,因为是同一种类型(类型兼容),可以进行赋值操作,貌似在演练场是报错的,报错同上❌ +// Type '23' is not assignable to type 'Color.Red'. let a: enumChildType = 23 ``` + ::: 使用场景: + - 枚举若未初始化,第一个值为0 - 枚举成员可以使用常量枚举表达式(表达式字面量、对前面枚举成员的引用、一元运算符、二元运算符、计算值等)初始化 - 若枚举常量表达式的结果为NaN或infinite,则会在编译阶段出错;直接赋值NaN或infinite不会出错 - 枚举是一个在运行时真正存在的对象,故而在兼容的情况下,枚举可以赋值给对象 -- 可以使用`keyof typeof enumVal`获取enumVal里面所有枚举成员的键的字符串字面量的类型联合 -- 数字枚举成员具有反向映射,例如`enum A { a }; let aa = A.a;// a的key为A[aa]; let nameOfa = A[aa];` +- 可以使用 `keyof typeof enumVal`获取enumVal里面所有枚举成员的键的字符串字面量的类型联合 +- 数字枚举成员具有反向映射,例如 `enum A { a }; let aa = A.a;// a的key为A[aa]; let nameOfa = A[aa];` ```typescript // 关于keyof和typeof解释 @@ -302,12 +319,12 @@ type ColorKeyType = keyof ColorType let k: ColorKeyType = 'Red' ``` - -14. `object`类型 +14. `object`类型 定义:非原始类型,表示除了number、string、boolean、bigint、symbol、null、undefined之外的类型 **object vs Object vs {}**: + - 只有非原始类型(null、undefined、boolean、number、string、symbol、bigint)才能赋给object类型 - 所有类型都能够赋值给Object和{}类型,undefined和null除外 - Object是object的父类型,也是object的子类型 @@ -317,6 +334,7 @@ let k: ColorKeyType = 'Red' 定义:使用大写字母开头,与相对应的小写版本类型一致 值: + - Number - String - Boolean @@ -324,7 +342,8 @@ let k: ColorKeyType = 'Red' - Object 使用: -- 用于创造函数 new 创建的对象。比如`new Boolean()`,是对象类型`Boolean`,而非某些特定的原始类型`boolean` + +- 用于创造函数 new 创建的对象。比如 `new Boolean()`,是对象类型 `Boolean`,而非某些特定的原始类型 `boolean` - 不属于基本类型,避免在任何时候用作一个类型 ```typescript @@ -353,12 +372,100 @@ let annarr: any[] = [1, true, '1'] const voi: void = undefined // never function nev (): never { - // 下面的条件只能为true,若是其他比较符,会报错:A function returning 'never' cannot have a reachable end point. + // 下面的条件只能为 true ,若是其他比较符(比如:1, 1 > 0等),会报错: + // A function returning 'never' cannot have a reachable end point. while(true) {} } ``` -## 字面量类型 +## Creating Types from Types + +### 条件类型 + +定义: + +- `type Cond = SomeType extends OtherType ? TrueType : FalseType;` +- 有助于描述输入和输出类型之间的关系 +- 类似JavaScript的条件表达式 + +```typescript +// 1 +interface IdLabel { + id: number; +} +interface NameLabel { + name: string; +} +// 此处对T进行了约束,只能是string和number类型 +type NameOrId = T extends number ? IdLabel : NameLabel; + +function createLabel(id: number): IdLabel; +function createLabel(name: string): NameLabel; +function createLabel(nameOrId: string | name): IdLabel | NameLabel; +function createLabel(idOrName: T): NameOrId { + throw 'unimplemented'; +} +// NameLabel +let a = createLabel('typescript'); +// IdLabel +let b = createLabel(2.8) +// NameLabel | IdLabel +let c = createLabel(Math.random() ? 'hello' : 42) + +// 2:条件类型约束 +// 此处由于T使用了类型约束,故才能使用等号右侧的形式,不然会报错: +// Type '"message"' cannot be used to index type 'T'. +// type MessageOf = T['message'] +type MessageOf = T extends { message: unknown; } ? T['message'] : never; + +interface Email { + message: string; +} +// string +type EmailMessageContents = MessageOf + +interface Dog { + bark(): void; +} +// never +type DogMessageContents = MessageOf + +// 3: 条件类型约束 +// 此处,若T是数组类型,则返回数组元素的类型,否则返回其本身 +type Flatten = T extends any[] ? T[number] : T; +// string +type Str = Flatten +// number +type Num = Flatten + +// 4:结合infer一起使用 +type GetReturnType = Type extends (...args: never[]) => infer Return ? Return : never; +// number +type Num = GetReturnType<() => number> +// string +type Str = GetReturnType<(x: string) => string> +// boolean[] +type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>; + +// 若此处的Type是一个重载函数这样了的具有多个调用签名的类型,则会返回最宽松的类型 +declare function stringOrNum(x: string): number; +declare function stringOrNum(x: number): string; +declare function stringOrNum(x: string | number): string | number; +// string | number +type T1 = ReturnType; + +// 5:条件类型分配 +type ToArray = Type extends any ? Type[] : never; +// string[] | number[] +type StrArrOrNumArr = ToArray + +// 若想得到: (string | number)[],则应该将extends两侧的内容用[]括起来 +type ToArrayNonDist = [Type] extends [any] ? Type[] : never; +// (string | number)[] +type ArrOfStrOrNum = ToArrayNonDist; +``` + +### 字面量类型 定义:字面量类型是类型(字符串、数字、布尔值)的一种更为具体的子类型 @@ -388,14 +495,16 @@ type ValidationResult = ``` 注意: + - 在实际应用中,字面量类型可以与联合类型、类型守卫、类型别名结合使用 - 字符串字面量类型还可用于区分函数重载,和普通函数重载一致 -## 模板字面量类型 +### 模板字面量类型 定义:模板字面量类型以字符串字面量类型为基础,且可以展开为多个字符串类型的联合类型 语法: + - 与JavaScript的模板字面量一致,只不过它是作用于类型上 - 该类型的值,本质上来说,是一个字符串类型 - 适用于数量较少的情况 @@ -432,6 +541,7 @@ const person = makeWatchedObject({ }) // 正常,因为eventName的值,是string & keyof Person => `${'firstName' | 'lastName' | 'age'}Changed` person.on('firstNameChanged', () => {}) + // 错误,因为不存在firstName person.on('firstName', () => {}) ``` @@ -467,11 +577,12 @@ person.on('ageChanged', (newAge) => { }) ``` -## symbol类型 +### symbol类型 -通过`Symbol('desc')`创建的值是不可改变且唯一的; +通过 `Symbol('desc')`创建的值是不可改变且唯一的; 用途: + - 用作对象属性的键 - 与计算出的属性名声明相结合来声明对象的属性和类成员 @@ -496,13 +607,14 @@ console.log(c[getClassNameSymbol](), 'get c') 内置的symbols😢😢😢 -## 交叉类型 +### 交叉类型 通俗理解:交叉类型,将多个类型合并为一个类型,包含了所有类型的特性(属性),同时拥有所有类型的成员(属性) -定义:使用`&`分隔类型,一般用于联合类型、接口交叉,若两者之间无交集,则该值为never类型 +定义:使用 `&`分隔类型,一般用于联合类型、接口交叉,若两者之间无交集,则该值为never类型 使用: + - 交叉类型**常用来定义公共的部分** - 原子类型合并成交叉类型,得到的类型是never,因为不能同时满足这些原子类型 - 交叉类型常用于将多个接口类型合并为一个类型,等同于接口继承(合并接口类型) @@ -567,15 +679,16 @@ let abc: ABC = { ``` -## 联合类型 +### 联合类型 -通俗理解:联合类型,即`存异`,可以是某种类型,也可以是另一种类型;是多个类型中的某一个(可以只满足一种类型即可),只能访问所有类型的共有属性 +通俗理解:联合类型,即 `存异`,可以是某种类型,也可以是另一种类型;是多个类型中的某一个(可以只满足一种类型即可),只能访问所有类型的共有属性 -定义:union,使用`|`分隔类型`string | number`,其值可以是声明类型的某一种`string`或者`number`。 +定义:union,使用 `|`分隔类型 `string | number`,其值可以是声明类型的某一种 `string`或者 `number`。 使用: + - 当不能(用类型推断)确定联合类型属于某一具体类型时,只能访问所有类型共有方法/属性。 -- 只有确定具体类型`if (typeof xxx === 'number') { xxx.toFixed() }`之后(比如使用条件语句、类型推断),才能访问特定类型的方法/属性 +- 只有确定具体类型 `if (typeof xxx === 'number') { xxx.toFixed() }`之后(比如使用条件语句、类型推断),才能访问特定类型的方法/属性 ```typescript // 对于变量的联合 @@ -616,11 +729,12 @@ type UU = U.A | U ``` -### 可辨识联合 +#### 可辨识联合 定义:同时使用单例类型、联合类型、类型守卫、类型别名这些语法概念,然后创建一个可辨识联合的高级模式,也叫做标签联合或代数数据联合 特征(3要素): + - 具有普通的单例类型属性,即可辨识的特征 - 一个类型别名包含了那些类型的联合,即联合 - 此属性上的类型守卫 @@ -659,21 +773,55 @@ function assertNever (x: never): never { } ``` -## 🟢索引类型(index types) +### 🟢索引类型(index types) 定义:使用索引类型后,编译器就能够检查使用了动态属性名(即属性不确定的类对象)的代码 索引类型查询操作符: + - 使用方式:`keyof T`,其结果为T上已知的公共*属性名*(键名)的联合(若含有索引签名时,则表示索引签名的类型的联合),当T的属性自动增减时,其结果也会自动增减 索引访问操作符: -- 使用方式:`T[K]`或者`T[K1 | K2]`,表示T的属性K的值,表示一种类型,其中需满足`K extends keyof T`,并且K是一个类型,而非一个值 + +- 使用方式:`T[K]`或者 `T[K1 | K2]`,表示T的属性K的值的类型 +- 其中需满足 `K extends keyof T`,并且K是一个类型(比如可以是:`type K = 'age'`),而非一个值(不能是:`const K = 'age'`) +- 若K是T中不存在的键,则会报相关的错误 + +```typescript +// 1 +type Person = { age: number; name: string; alive: boolean; }; +// number,这里的'age'是一个字符串字面量类型 +type Age = Person["age"] + +// 2 +// string | number,这里的'age','name'是一个字符串字面量类型 +type I1 = Person["age" | "name"]; +// number | string | boolean +type I2 = Person[keyof Person]; +type AliveOrName = 'alive' | 'name' +// string | boolean +type I3 = Person[AliveOrName]; + +// 3 +const MyArray = [ + { name: 'alice', age: 15 }, + { name: 'bob', age: 23 }, + { name: 'eve', age: 38 } +]; +// 这里是获取数组的项的类型,即:{ name: string; age: number; } +type Person = typeof MyArray[number]; +// 这里是获取数组的项内部的age属性的类型,即:number +type Age = typeof MyArray[number]['age'] +``` 索引签名: -- 指的是类似接口中的属性名,但是其属性名不是确切的,使用方式为`[key: string]: T`,当类型不正确时,报错An index signature parameter type must be 'string', 'number', 'symbol', or a template literal type. + +- 指的是类似接口中的属性名,但是其属性名不是确切的,使用方式为 `[key: string]: T`,当类型不正确时,报错An index signature parameter type must be 'string', 'number', 'symbol', or a template literal type. +- 用于声明尚未提前声明的属性类型 ::: code-group -```typescript [索引类型] + +```typescript // 索引类型查询即keyof T,它的值为T的所有键名,k继承了T的所有键名(但是k的值只能是T中有的各种集合,无自身的值) // 索引访问即T[K],他为😊一个类型 ,类型值为T[K],若T[K]是反正某一种类型,则T[K]就是该种类型 function pluck (o: T, propertyNames: K[]): T[K][] { @@ -696,7 +844,7 @@ let carProps: keyof Car let makeAndModel: string[] = pluck(taxi, ['manufacturer', 'model']) ``` -```typescript [keyof的妙用] +```typescript function getValue(o: object, k: string) { // Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. // No index signature with a parameter of type 'string' was found on type '{}'. @@ -710,7 +858,7 @@ function getValue(o: T, k: K) { } ``` -```typescript [字符串索引签名与keyof的使用] +```typescript interface Dictionary { // 字符串索引签名的类型 [key: string]: T @@ -721,7 +869,7 @@ let key: keyof Dictionary let value: Dictionary['foo'] ``` -```typescript [数字索引签名与keyof的使用] +```typescript interface Dictionary { // 数字索引签名的类型 [key: number]: T @@ -733,40 +881,128 @@ let value: Dictionary['foo'] // 此处值为number let value: Disctionary[42] ``` + ::: -## 映射类型😢😢😢 +### 映射类型 定义: + - 从旧类型中创建新类型的一种方式,新类型以相同的方式去转换旧类型里的每个属性,内置类型就是映射类型来的 +- 映射类型是一种泛型类型,使用(通常是通过keyof创建的)键的联合,从而遍历键创建一个新类型 - 映射类型类似一个函数,将一个类型,通过该函数(映射类型)转换成新的类型 -- 映射类型基于索引签名`[ xxx ]: type;` +- 映射类型基于索引签名 `[ xxx ]: type;` +- 映射修饰符:`readonly`、`?`,可配合`+`(默认), `-`使用 +- 可通过as重新映射类型中的键 注意: + - 若想给映射类型添加新成员,需要结合交叉类型一起使用 -- 对于同态转换(Readonly、Partial、Pick,指的是需要输入类型来拷贝属性,类似下面示例中的`T[P]`,Record不是,因为他不需要输入类型),编译器知道在添加任何新属性之前拷贝所有存在的属性修饰符 +- 对于同态转换(Readonly、Partial、Pick,指的是需要输入类型来拷贝属性,类似下面示例中的 `T[P]`,Record不是,因为他不需要输入类型),编译器知道在添加任何新属性之前拷贝所有存在的属性修饰符 ::: code-group -```typescript [Readonly] + +```typescript [映射修饰符] +type OptionsFlags = { + [Property in keyof Type]: boolean; +}; + +type CreateMutable = { + // 此处表示将只读变成可读写 + -readonly [Property in Keyof Type]: Type[Property]; +} + +type Concrete = { + // 此处表示将可选变成必填 + [Property in keyof Type]-?: Type[Property]; +} + +type Features = { + darkMode: () => void; + newUserProfile: () => void; +}; +// { darkMode: boolean; newUserProfile: boolean; } +type FeatureOptions = OptionsFlags; +``` + +```typescript [通过as重新映射] +type MappedTypeWithNewProperties = { + // 将Properties重新映射为NewKeyType + [Properties in keyof Type as NewKeyType]: Type[Properties]; +} + +// 1 +type Getters = { + [Property in keyof Type as `get${Capitalize}`]: () => Type[Property]; +}; + +interface Person { + name: string; + age: number; + location: string; +} + +// { getName: () => string; getAge: () => number; getLocation: () => string; } +type LazyPerson = Getters; + +// 2 +type RemoveKindField = { + [Property in keyof Type as Exclude]: Type[Property]; +}; + +interface Circle { + kind: 'circle'; + radius: number; +} + +// { radius: number; } +type KindlessCircle = RemoveKindField; + +// 3: 映射任意并集,不仅仅是string | number | symbol,不过最终还是要映射成这三种之一 +type EventConfig = { + // 此处,若单纯是[E in Events],则会报错: + // Type 'Events' is not assignable to type 'string | number | symbol'. + // 是因为对象的键必须是上面这三种类型之一 + // 但是使用as进行映射成三种类型之一,就不会报错 + // 这里是最终将E映射成了E['kind'] + [E in Events as E['kind']]: (event: E) => void; +}; + +type SquareEvent = { kind: 'square', x: number, y: number }; +type CircleEvent = { kind: 'circle', radius: number }; +// { square: (event: SquareEvent) => void; circle: (event: CircleEvent) => void; } +type Config = EventConfig; + +// 4: 和条件类型使用 +type ExtractPII = { + [Property in keyof Type]: Type[Property] extends { pii: true } : true ? false; +}; + +type DBFields = { + id: { format: 'incrementing' }; + name: { type: string; pii: true }; +}; + +// { id: false, name: true } +type ObjectsNeedingGDPRDeletion = ExtractPII; +``` + +```typescript [内置类型的例子] +// 内置类型的例子: type Readonly = { readonly [P in keyof T]: T[P] } -``` -```typescript [Partial] type Partial = { [P in keyof T]?: T[P] } -``` -```typescript [Pick] type Pick = { [P in K]: T[P] } -``` -```typescript [Record] type Record = { [P in K]: T } ``` + ::: ## 内置工具类 @@ -786,17 +1022,20 @@ type Record = { 13. `InstanceType`:从构造函数类型Type的实例类型 来构造一个类型 14. `ThisParameterType`:从函数类型Type中提取this参数的类型,若函数类型不包含this参数,返回unknown类型😢😢😢 15. `OmitThisParameter`:从类型Type中剔除this参数,若未声明this参数,结果类型为Type,否则构建一个不带this参数的类型。泛型会被忽略,且只有最后的重载签名会被采用😢😢😢 -16. `ThisType`:不会返回一个转换后的类型,仅作为上下文this类型的一个标记。若使用该类型,需启用`--noImplicitThis`😢😢😢 +16. `ThisType`:不会返回一个转换后的类型,仅作为上下文this类型的一个标记。若使用该类型,需启用 `--noImplicitThis`😢😢😢 17. `Uppercase`:将字符串中的每个字符转为大写字母 18. `Lowercase`:将字符串的每个字符转为小写字母 19. `Capitalize`:将字符串的首字母转换为大写字母 20. `Uncapitalize`:将字符串的首字母转为小写字母 +21. `Awaited`: 用于await async函数、Promise的then方法,同时可以递归解包Promise(即`Promise(Promise(...))`这种形式) 注意: -- `[P in keyof T]-? : T[P]`,其中的`-?`表示移除可选标识符,表示必填;同样`+?`表示加上可选标识符,表示可选 + +- `[P in keyof T]-? : T[P]`,其中的 `-?`表示移除可选标识符,表示必填;同样 `+?`表示加上可选标识符,表示可选 ::: code-group -```typescript [内置工具类型的映射] + +```typescript [内置工具类型] // partial:可选 type Partial = { [K in keyof T]?: T[K]; @@ -855,6 +1094,7 @@ let p: Partial = { title: 'hello' } ``` + ```typescript [Required] interface Todo { title: string @@ -866,6 +1106,7 @@ let p: Required = { description: 'desc' } ``` + ```typescript [Readonly] interface Todo { title: string @@ -879,6 +1120,7 @@ let p: Readonly = { // Cannot assign to 'title' because it is a read-only property. p.title = 'edit title' ``` + ```typescript [Record] interface Todo { title: string @@ -892,6 +1134,7 @@ let p: Record = { body: { title: 'a', description: 'desc' } } ``` + ```typescript [Pick] interface Todo { title: string @@ -905,6 +1148,7 @@ let p: Pick = { footer: 'b' } ``` + ```typescript [Omit] interface Todo { title: string @@ -917,22 +1161,26 @@ let p: Omit = { description: 'a' } ``` + ```typescript [Exclude] // 'b' | 'c' type T0 = Exclude<'a' | 'b' | 'c', 'a'> // string | number type T2 = Exclude void), Function> ``` + ```typescript [Extract] // 'a' type T0 = Extract<'a' | 'b' | 'c', 'a'> // () => void type T2 = Extract void), Function> ``` + ```typescript [NonNullable] // string | number type T = NonNullable ``` + ```typescript [Parameters] declare function f1 (arg: { a: number, b: string }): void // [] @@ -953,12 +1201,14 @@ type T6 = Parameters // unknown[] type T7 = Parameters ``` + ```typescript [ConstructorParameters] // [message?: string | undefined] type T0 = ConstructorParameters // 报错,Type 'Function' does not satisfy the constraint 'abstract new (...args: any) => any'. type T1 = ConstructorParameters ``` + ```typescript [ReturnType] // string type T0 = ReturnType<() => string> @@ -968,26 +1218,43 @@ type T1 = ReturnType<(()) => T> type T2 = ReturnType type T3 = ReturnType ``` + ```typescript [Uppercase] type Greeting = 'hello' // HELLO type TitleGreeting = Uppercase ``` + ```typescript [Lowercase] type Greeting = 'HeLlo' // hello type TitleGreeting = Lowercase ``` + ```typescript [Capitalize] type Greeting = 'heLlo' // HeLlo type TitleGreeting = Capitalize ``` + ```typescript [Uncapitalize] type Greeting = 'HeLlo' // heLlo type TitleGreeting = Uncapitalize ``` + +```typescript [Awaited] +// v4.5+ +// string +type A = Awaited> + +// number +type B = Awaited>> + +// number | boolean +type C = Awaited> +``` + ::: ## 类型相关 @@ -997,10 +1264,12 @@ type TitleGreeting = Uncapitalize **typeof**: 定义: + - 获取右侧标识符(变量或属性值)的类型 - 对于获取有些表达式、函数调用、类型的类型会报错,即不能使用typeof XXX的形式 语法: + ```typescript // 报错,获取dom元素div的类型(右侧不能是函数调用) type DivType = typeof document.createElement('div') @@ -1021,11 +1290,44 @@ let p: Person = { type pType = typeof p ``` +**keyof**: + +定义: + +- 生成对象类型的键的string、number字面量类型的联合 + +```typescript +// 1 +type Point = { x: number, y: number } +// 'x' || 'y' +type P = keyof Point; + +// 2 +type Arrayish = { [n: number]: unknown }; +// number +type A = keyof Arrayish; + +// 3:此处包含number,是因为JavaScript对象的键会被强制转为string +type Mapish = { [k: string]: boolean }; +// string | number +type M = keyof Mapish; + +``` + +**in**: + +用法: + +- 用在索引签名中:`{ [Key in StrUnion]: string }`, `{ [Key in keyof Obj]: string}` +- 用在类型守卫中(函数内部),之后就能够使用对应的方法和属性了:`if ('age' in Person) { console.log(Person.age)}` +- 用在类型守卫中(函数返回值类型):`function isFish(pet: Fish | Bird): pet is Fish { return (pet as Fish).swim !== undefind }` + **infer**: > 参考:https://www.jianshu.com/p/707a304d7752 -定义:用于条件语句中,获取推断的类型,基本类似`T extends U ? X : Y`,其中: +定义:用于条件语句中,获取推断的类型,基本类似 `T extends U ? X : Y`,其中: + - infer只能在条件类型的extends字句中使用 - infer得到的类型只能在true(即X)语句中使用 @@ -1072,7 +1374,7 @@ type I5 = InferString<'Hello, jade'>; // infer First 或者说 I5: 'H' 作用:在鼠标悬浮在使用该类型的变量的时候,会显示出该类型的描述信息 -语法:在类型之前使用`/** */`形式的注释 +语法:在类型之前使用 `/** */`形式的注释 ```typescript /** User包括name和age两个属性 */ @@ -1094,9 +1396,10 @@ type User = { name: string, age: number } 定义:手动指定一个值的类型,就可以调用该类型的方法而不在编译时报错(但在运行时该报错还是会报错) 语法: + - `value as type` - `value` -- `value!`:后缀表达式操作符`!`,用于1️⃣排除该值可能是null、undefined,以及2️⃣表明value会被明确的赋值 +- `value!`:后缀表达式操作符 `!`,用于1️⃣排除该值可能是null、undefined,以及2️⃣表明value会被明确的赋值 - `value as Type1 as OtherType`:双重断言,先将value断言为type1(比如any或unknown,因为any可以断言为任何类型,同时任何类型都可以断言为any),然后又将type1的类型断言为OtherType ```typescript @@ -1115,6 +1418,7 @@ const greaterThan2: number = arrayNumber.find(num => num > 2) as number ``` 场景(常用类型断言): + - 联合类型可以被断言为其中的一个类型 - 父类可以被断言为子类 - 任何类型都可以被断言为 any @@ -1122,6 +1426,7 @@ const greaterThan2: number = arrayNumber.find(num => num > 2) as number - 若想 A 能够被断言为 B,只需 A 兼容 B,或 B 兼容 A;兼容指的是结构兼容。若 A 兼容 B,那么 A 可以被断言为 B,B 也可以被断言为 A,举例,因为子类结构兼容父类,所以子类可以被断言为父类,父类也可以被断言为子类 使用: + - typescript 类型之间的对比只会比较他们最终的结构,而忽略他们定义时的关系。 ```typescript @@ -1145,7 +1450,8 @@ if ((pet as Fish).swim) { ``` **扩展**: -- 类型拓宽:在var、let定义的变量中,若未显式声明类型,该变量的类型会被自动推断并拓宽,比如`let a = 1`,则a的类型会扩宽为number,同时值为null/undefined,会被拓宽为any(即使是严格空值模式下,可能有些老浏览器不支持)。将这种未显式声明类型的变量(或者是更宽泛的类型)赋值给某些具体的类型时,会发生错误(比如扩宽为string的变量,就不能赋值给具体类型`'1' | '2'`的变量。反之可以将具体的类型赋值给符合条件的更宽泛的类型。 + +- 类型拓宽:在var、let定义的变量中,若未显式声明类型,该变量的类型会被自动推断并拓宽,比如 `let a = 1`,则a的类型会扩宽为number,同时值为null/undefined,会被拓宽为any(即使是严格空值模式下,可能有些老浏览器不支持)。将这种未显式声明类型的变量(或者是更宽泛的类型)赋值给某些具体的类型时,会发生错误(比如扩宽为string的变量,就不能赋值给具体类型 `'1' | '2'`的变量。反之可以将具体的类型赋值给符合条件的更宽泛的类型。 - 类型缩小:使用类型守卫、`===`、其他控制流语句(if、三目运算符、switch)将宽泛的类型收敛为更具体的类型 ```typescript @@ -1172,12 +1478,13 @@ const obj3 = { ### 类型兼容 定义:由于typescript使用的是结构类型的系统,当比较两种不同的类型时,并不在乎他们从何而来,只会比较他们的结构是否兼容(包含或被包含),若两者之间所有成员的类型都是兼容的,则他们也是兼容的 + - 对于**值**来说,当前有两个结构x和y,若想x兼容y(例如将y类型赋值给x类型的变量),则必须保证y类型必须包含(多于)x类型的结构(只能多,但不能少),即结构多的赋值给结构少的 - 对于**函数**来说,当前有两个函数x和y,他们除函数参数外其余都相等,若想x兼容y,必须保证y的函数参数被包含(小于)x的函数参数,即参数少的,可以赋值给参数多的,*相当于调用的时候,可以省略参数* - 对于**函数**来说,当前有两个函数x和y,他们除返回值外其余都相等,若想x兼容y,和值兼容类似,则必须保证y类型必须包含(多余)x类型的结构(只能多,不能少),即结构多的赋值给结构少的 - 当成员的修饰符为private、protected时,只有他们来自同一处声明时(实例的继承链都继承自同一个对象、都是某个对象的实例),他们的类型才是兼容的 -函数参数的双向协变:定义一个函数,该函数含有一个函数类型的参数,且该参数是非具体的(泛的),当调用时,却传入了一个具体的函数类型的参数,它是不稳定的,这就叫做函数的双向协变。可以启用`strictFunctionTypes`选项,让typescript在这种情形下报错。 +函数参数的双向协变:定义一个函数,该函数含有一个函数类型的参数,且该参数是非具体的(泛的),当调用时,却传入了一个具体的函数类型的参数,它是不稳定的,这就叫做函数的双向协变。可以启用 `strictFunctionTypes`选项,让typescript在这种情形下报错。 可选参数和剩余参数:比较函数兼容性时,可选参数和必须参数是可互换的,源类型(调用的)上有额外的可选参数不是错误,目标类型(参照,原函数)的可选参数在源类型没有对应的参数也不是错误,缺少时,相当于该值是undefined @@ -1186,19 +1493,23 @@ const obj3 = { 枚举类型和数字类型互相兼容,但是不同枚举类型之间是互不兼容的 对于类之间的兼容性: + - 只会比较类的实例成员,静态函数和构造函数不在比较的范围内 - 类的私有成员和保护成员会影响兼容性,当目标类型(比如父类)包含一个私有成员,则源类型(比如子类)则必须包含来自同一个类的这个私有成员(只有继承关系才会来自同一个类)。这表明子类可以赋值给父类,但是不能赋值给其他和父类同结构的类 对于泛型之间的兼容性: + - 泛型中的类型参数,若是结构中的内容与类型参数无关,只要两个泛型结构兼容,则类型参数的类型不会影响两者兼容性 - 泛型的表示不同,只要结构类型都相同,也是不影响兼容的,比如泛型T和泛型U 子类型和赋值:typescript有两者兼容性的方式,就是子类型和赋值 + - 赋值扩展了子类型的兼容性,增加了一些规则,允许和any来回赋值,以及enum和number来回赋值 - 类型兼容性实际上是由赋值兼容性控制,即使是在implements和extends语句中😢😢😢 ::: code-group -```typescript [值兼容] + +```typescript interface Named { name: string } @@ -1207,7 +1518,8 @@ let y = { name: 'alice', location: 'beijing' } // x能够兼容y,因为y的结构包含x的结构,对于对象,可以多不能少 x = y ``` -```typescript [函数参数兼容] + +```typescript let x = (a: number) => 0 let y = (a: number, s: string) => 0 // 兼容,对于函数参数,可以少不能多 @@ -1215,7 +1527,8 @@ y = x // 不兼容 x = y ``` -```typescript [函数返回值兼容] + +```typescript let x = (a: number) => ({ name: 'alice' }) let y = (a: number) => ({ name: 'alice', location: 'beijing' }) // 兼容,对于返回值,可以多不能少 @@ -1223,7 +1536,8 @@ x = y // 不兼容 y = x ``` -```typescript [函数的双向协变] + +```typescript interface Event { timestamp: number } interface MouseEvent extends Event { x: number, y: number } interface KeyEvent extends Event { keyCode: number } @@ -1236,7 +1550,8 @@ listenEvent('Mouse', (e: MouseEvent) => cosole.log(e.x)) listenEvent('Mouse', (e: Event) => console.log((e as MouseEvent).x)) listenEvent('Mouse', ((e: MouseEvent) => console.log(e.x)) as (e: Event) => void) ``` -```typescript [可选参数和剩余参数] + +```typescript // 目标函数 function invoke(callback: (...args: any[]) => void) { callback() } // 源函数有可选参数,不会报错,当未传入时,值为undefined @@ -1248,7 +1563,8 @@ function invoke(callback: (x: any, y?: any, z?: any) => void) { } invoke((x, y) => console.log(x + ',' + y)) ``` -```typescript [泛型兼容性] + +```typescript // 泛型参数对泛型兼容性的影响1 interface Empty { name: string @@ -1270,29 +1586,35 @@ x = y; y = x; ``` + ::: ### 类型守卫 前景: + - 联合类型中,若要访问非共同拥有的成员,每次调用都需要使用类型断言才会工作 定义: + - 类型守卫机制可以避免多次断言的问题,当通过类型检查之后,该代码块下的所有该变量就会判定为该类型(类型兼容) - 类型守卫是一些表达式,会在运行时检查,以确保在某个作用域下的类型 类型守卫使用方式: + - 类型判定:定义一个函数,返回值是一个类型谓词(`parameterName is Type`) -- in操作符:用法为`n in x`,其中n是一个字符串字面量或字符串字面量类型,x是一个联合类型,用于遍历可枚举类型的属性,生成对象的key,常用于对象的索引签名中(对象的键不确定的情况);也可以表条件,条件为真表示有一个可选的或必须的属性n,条件为假表示有一个可选的或不存在的属性n -- typeof类型守卫:typescript会将`typeof v === 'typename'`和`typeof v !== 'typename'`自动识别为类型守卫,且typename的值必须是number、string、boolean、symbol类型,其他类型会当成一个普通的表达式而已(不会当初类型守卫) -- instanceof类型守卫:通过构造函数来细化类型的一种方式,用法为`n instanceof x`,其中x必须是一个构造函数(类名);typescript将细化为构造函数x的prototype属性的类型(非any类型),构造签名返回的类型的联合 +- in操作符:用法为 `n in x`,其中n是一个字符串字面量或字符串字面量类型,x是一个联合类型,用于遍历可枚举类型的属性,生成对象的key,常用于对象的索引签名中(对象的键不确定的情况);也可以表条件,条件为真表示有一个可选的或必须的属性n,条件为假表示有一个可选的或不存在的属性n +- typeof类型守卫:typescript会将 `typeof v === 'typename'`和 `typeof v !== 'typename'`自动识别为类型守卫,且typename的值必须是number、string、boolean、symbol类型,其他类型会当成一个普通的表达式而已(不会当初类型守卫) +- instanceof类型守卫:通过构造函数来细化类型的一种方式,用法为 `n instanceof x`,其中x必须是一个构造函数(类名);typescript将细化为构造函数x的prototype属性的类型(非any类型),构造签名返回的类型的联合 注意事项: -- 对于包含null的参数联合类型,需要使用类型守卫去除null;方法包括:条件语句`if (sn === null) {}`, 短路运算符`return sn || 'default'` -- 若编译器不能自动去除null或undefined,需要手动使用类型断言去除,方式是在变量后面添加`!`,例如`name!.charAt(0)` + +- 对于包含null的参数联合类型,需要使用类型守卫去除null;方法包括:条件语句 `if (sn === null) {}`, 短路运算符 `return sn || 'default'` +- 若编译器不能自动去除null或undefined,需要手动使用类型断言去除,方式是在变量后面添加 `!`,例如 `name!.charAt(0)` ::: code-group -```typescript [类型判定] + +```typescript // 定义类型判定函数 function isFish (pet: Fish | Bird): pet is Fish { return (pet as Fish).swim !== undefined @@ -1305,7 +1627,8 @@ if (isFish(pet)) { pet.fly() } ``` -```typescript [in操作符] + +```typescript function move (pet: Fish | Bird) { if ('swim' in pet) { return pet.swim() @@ -1313,7 +1636,8 @@ function move (pet: Fish | Bird) { return pet.fly() } ``` -```typescript [instanceof类型守卫] + +```typescript function getRandomPadder () { return Math.random() < 0.5 ? new SpaceRepeatingPadder(4) : new StringPadder(' ') } @@ -1327,6 +1651,7 @@ if (padder instanceof StringPadder) { padder; } ``` + ::: ### 类型别名 @@ -1336,9 +1661,10 @@ if (padder instanceof StringPadder) { 用法:`type typeName = typexxx` 注意: + - 类型别名可以是泛型,可以添加类型参数并且在别名声明的右侧typexxx传入泛型参数 - 可以在右侧typexxx中的属性里面引用自身,但类型别名不能出现在声明右侧的任何地方(属性除外) -- 和交叉类型`&`一起使用时,可以创建一些古怪的类型 +- 和交叉类型 `&`一起使用时,可以创建一些古怪的类型 ```typescript // 使用泛型 @@ -1356,12 +1682,13 @@ type Yikes = Array ``` **类型别名 vs 接口**: + - 两者都能描述对象/函数的类型: - 接口:`interface Itf = { (n: number): void }` - 类型别名:`type Itf = (n: number) => void` - 接口无法描述一个原始值、联合类型、元组类型,但类型别名可以 - 接口可以定义多次并自动合并为单个接口,类型别名不可以 -- 两者都能进行扩展,且能相互扩展,比如`interface Itf {x: number }`: +- 两者都能进行扩展,且能相互扩展,比如 `interface Itf {x: number }`: - 接口的扩展是继承:通过extends实现,`interface Itf2 extends Itf { y: number}` - 类型别名的扩展是交叉类型,通过&实现,`type Itf2 = Itf & {y: number}` - 接口创建了一个新的名字,可以在其他任何地方使用;而类型别名并非创建一个新名字,且错误信息不会使用别名 @@ -1409,17 +1736,20 @@ let v = new ScientificCalculator(2) ## 接口 定义: + - ts的核心原则之一是对值具有的结构进行类型检查(鸭式辩型法/结构性子类型化),接口的作用是为这些类型命名或定义契约 使用: + - 接口 interface 可用于定义对象的类型,且对象的属性个数必须完全和接口的属性个数相同(不能增加、减少属性),除非: - 1. 接口属性是可选属性`name?:string;`(减少)。 - 2. 若接口包括一个任意类型的属性`[propName: string]: string | number;`,则对象属性可以有无限多个(增加),此时接口中与任意类型属性的 key`propName`同类型`string`的属性,必须是那个任意类型`string | number`的子类型`string`或`number` + 1. 接口属性是可选属性 `name?:string;`(减少)。 + 2. 若接口包括一个任意类型的属性 `[propName: string]: string | number;`,则对象属性可以有无限多个(增加),此时接口中与任意类型属性的 key `propName`同类型 `string`的属性,必须是那个任意类型 `string | number`的子类型 `string`或 `number` 3. 特例,使用变量的方式,将变量传给参数,而不是直接在参数定义一个变量字面量(对象字面量) - 接口可用于描述JavaScript各种类型,不管是普通的对象,也可以是函数 ::: code-group -```typescript [任意个接口属性] + +```typescript interface Int { // 普通属性,这些都不会报错,因为propName`『接口的key可以是任意类型』`是一个number,所以不会进行匹配 name: string; @@ -1430,7 +1760,8 @@ interface Int { 1: true; } ``` -```typescript [特例] + +```typescript interface SquareConfig { color?: string; width?: number; @@ -1458,10 +1789,10 @@ let mySquare = createSquare(squareOptions); **鸭子辩型法**:像鸭子一样走路,并且嘎嘎叫的就是鸭子,即具有鸭子特征(具备最小结构)的认为它就是鸭子 **绕过类型检查的方式**: -- 鸭子辨型法 -- 类型断言`obj as SquareConfig` -- 索引签名`[key: string]: any` +- 鸭子辨型法 +- 类型断言 `obj as SquareConfig` +- 索引签名 `[key: string]: any` ### 内部结构解释 @@ -1472,6 +1803,7 @@ let mySquare = createSquare(squareOptions); 语法:`width?: number;` 使用: + - 可选属性可以对可能存在的属性进行预定义 - 可以捕获引用了不存在的属性时的错误。当明确了该类型是某个接口时,只能引用该接口已有的属性 @@ -1508,7 +1840,7 @@ let p: Point = { x: 10, y: 20 } p.x = 20 ``` -引申:`ReadonlyArray`类型,只读数组类型,和上面定义类似,只能读取,且不能赋值给`Array`类型 +引申:`ReadonlyArray`类型,只读数组类型,和上面定义类似,只能读取,且不能赋值给 `Array`类型 ```typescript let a: number[] = [1, 2, 3] @@ -1522,6 +1854,7 @@ a = ra 3. 接口描述函数类型 语法:类似一个只有参数列表和返回值类型的函数定义,如下: + ```typescript interface SearchFunc { (source: string, subString: string): boolean; @@ -1539,15 +1872,16 @@ mySearch = function (src: string, sub: string) { 4. 接口描述具有索引的对象类型(数组、map等) 前置描述: + - typescript支持两种索引签名,字符串和数字 - 同一个接口中,每种类型的索引,只能存在一次,不然会报错:Duplicate index signature for type 'number'. - 当两种索引签名同时存在时,数字索引的值 必须是 字符串索引的值的子类型,因为当引用时,a[0]等同于a['0'] - 当存在了一种类型的索引签名时,其他具体的对象属性的key若和该种索引类型一致,则对应的值,必须是该种索引(子)类型 - 设置只读索引,在语句前面,加上关键字readonly - ::: code-group -```typescript [索引签名语法] + +```typescript interface StringArray { // 下面这个是数字索引签名,索引签名,就是`[index: number]`这部分 [index: number]: string; @@ -1559,7 +1893,7 @@ myArr = ['bob'] let str: string = myArr[0] ``` -```typescript [两种索引签名同时存在的情形] +```typescript class Animal { name: string; } @@ -1580,7 +1914,8 @@ interface Okay { } ``` -```typescript [只读索引] + +```typescript interface ReadonlyStringArray { readonly [index: number]: string; } @@ -1589,18 +1924,21 @@ let myArr: ReadonlyStringArray = ['bob'] // Index signature in type 'ReadonlyStringArray' only permits reading. myArr[0] = 'tom' ``` + ::: -5. 接口描述类类型 +5. ``接口描述类类型`` 使用: + - 接口描述了类的公共部分,不会检查类是否具有私有成员 - 可以在接口中描述一个方法,然后在类中来实现 - 类实现接口时,只会对类的实例进行类型检查,constructor存在于类的静态部分,所以不会进行检查 实现: ::: code-group -```typescript [第一种方式] + +```typescript interface ClockConstructor { new (hour: number, minute: number): ClockInterface; } @@ -1634,7 +1972,7 @@ let analog = createClock(AnalogClock, 7, 32); ``` -```typescript [第二种方式] +```typescript interface ClockConstructor { new (hour: number, minute: number); } @@ -1651,6 +1989,7 @@ const Clock: ClockConstructor = class Clock implements ClockInterface { }; ``` + ::: 6. 接口继承 @@ -1658,6 +1997,7 @@ const Clock: ClockConstructor = class Clock implements ClockInterface { 定义:接口可以相互继承,即能够从一个接口复制成员到另一个接口,从而更灵活将接口分割到可重用的模块中 语法如下: + ```typescript interface Shape { color: string; @@ -1682,6 +2022,7 @@ square.sideLength = 20 定义:接口能够描述JavaScript中丰富的类型,比如一个对象可以同时作为函数、对象使用,并拥有额外的方法/属性 语法: + ```typescript interface Counter { // 定义一个函数 @@ -1708,6 +2049,7 @@ c.interval = 5.0; 8. 接口继承类 使用 + - 接口继承类时,会继承类的成员(包括private和protected),但是不包括其具体的实现 - 当一个接口继承了拥有private/protected成员的类时,该接口只能被该类或该类的子类所实现 @@ -1741,6 +2083,7 @@ class ImageControl implements SelectableControl { ## 函数 定义:声明式和函数表达式形式,如下: + ```typescript // 函数声明 function sum(x: number, y: number): number { @@ -1781,20 +2124,23 @@ const obj: AddT = { ``` 场景: + - 用于实现抽象类、模拟类、信息隐藏、模块 - 虽然ts中支持类、命名空间、模块,然而函数仍然是主要定义行为的方式 使用: + - 函数传入的参数类型必须是和定义时一致 -- 函数的可选参数,必须在必须参数后面`(x: number, y?: number)` -- 函数无返回值,其返回值类型为`void` -- 函数参数的默认值`(x: number = 1, y: number)`,出现位置无特殊要求,但是,若不想传某些值,必须用`undefined`作为占位,这样就会跳过对应的值,后面的值就能够传过去了。在必须参数后面的带默认值的参数都是可选的(其他位置要传),可不传任何值。 -- 函数定义中参数也可用剩余参数,必须在参数的最后一个`(x: number, ...y: any[])`,用于获取剩下的传入参数。其中在函数内调用时,y 是一个数组 +- 函数的可选参数,必须在必须参数后面 `(x: number, y?: number)` +- 函数无返回值,其返回值类型为 `void` +- 函数参数的默认值 `(x: number = 1, y: number)`,出现位置无特殊要求,但是,若不想传某些值,必须用 `undefined`作为占位,这样就会跳过对应的值,后面的值就能够传过去了。在必须参数后面的带默认值的参数都是可选的(其他位置要传),可不传任何值。 +- 函数定义中参数也可用剩余参数,必须在参数的最后一个 `(x: number, ...y: any[])`,用于获取剩下的传入参数。其中在函数内调用时,y 是一个数组 - 函数重载,允许一个函数接受不同数量或类型的参数,并进行不同的处理;ts 会优先从最前面的函数定义开始匹配,*若多个函数定义有包含关系,需要把精确的函数定义写在前面* -- 异步函数的返回值,用`Promise`定义,这个适用于promise和async...await,其中T是resolve的返回值类型 +- 异步函数的返回值,用 `Promise`定义,这个适用于promise和async...await,其中T是resolve的返回值类型 ::: code-group -```typescript [函数重载] + +```typescript function reverse(x: number): number; function reverse(x: string): string; // 函数重载中,最后一个出现的必须是函数的实现 @@ -1808,7 +2154,7 @@ function reverse(x: number | string): number | string | void { } ``` -```typescript [函数内的this] +```typescript interface Card { suit: string; card: number; @@ -1839,7 +2185,7 @@ alert("card: " + pickedCard.card + " of " + pickedCard.suit); ``` -```typescript [回调参数的this] +```typescript // 此例不能编译成功 interface UIElement { addClickListener(onclick: (this: void, e: Event) => void): void; @@ -1854,7 +2200,7 @@ uiElement.addClickListener(h.onClickGood); ``` -```typescript [异步函数的返回值] +```typescript // 若没有返回数据,则使用`Promise` function queryData(): Promise { return new Promise(resolve => { @@ -1865,6 +2211,7 @@ function queryData(): Promise { } queryData().then(data => console.log(data)) ``` + ::: ## 类 @@ -1872,6 +2219,7 @@ queryData().then(data => console.log(data)) 1. 类的修饰符 修饰符类型: + - public:可以自由的访问,若未具体声明,则默认为public类型 - private:不能在被声明的类的外部访问,不能通过实例访问 - protected:能够在继承的类内部被访问,不能通过类的实例访问;当构造函数是protected时,则不能被实例化,只能被继承,然后被继承的类实例化 @@ -1880,6 +2228,7 @@ queryData().then(data => console.log(data)) 2. 参数属性 定义: + - 参数属性可以在一个地方同时定义并初始化成员属性,将声明和赋值合并到一处 - 参数属性通过给构造函数添加一个访问修饰符(public、private、protected)来声明,修饰符不能省略 @@ -1936,6 +2285,7 @@ if (employee.fullName) { 解释:一个类若从另一个类继承了属性和方法,则该类称为子类/派生类,另一个类成为基类/超类/父类 场景: + ```typescript class Animal { name: string; @@ -1962,9 +2312,10 @@ tom.move(34); 6. 抽象类 -定义:用关键字`abstract`定义抽象类和抽象类内的抽象方法,一般作为类的基类使用,一般不会直接被实例化 +定义:用关键字 `abstract`定义抽象类和抽象类内的抽象方法,一般作为类的基类使用,一般不会直接被实例化 使用: + - 抽象类可以有抽象函数,也可以包括具体实现的函数 - 抽象类的抽象方法(必须用abstract修饰)可以包含修饰符(不能是private),且必须在继承类中实现其具体细节 - 抽象类内的方法,若无abstract修饰符,则必须有具体的实现 @@ -2017,11 +2368,12 @@ let point3d: Point3d = {x: 1, y: 2, z: 3}; ## 泛型 -> 参考: -> https://zhuanlan.zhihu.com/p/149767010 -> https://zhuanlan.zhihu.com/p/141887346 +> 参考: +> https://zhuanlan.zhihu.com/p/149767010 +> https://zhuanlan.zhihu.com/p/141887346 定义: + - 使用泛型来定义可重用的组件,一个组件可以支持多个类型的数据(不仅是目前的,还可能是未来的),这样用户就可以以自己的数据类型来使用组件 - 泛型,即类型的函数(使用类似函数的用法,比如接收多个类型变量作为参数),声明一个类型变量,在类型函数代码中使用这个类型变量。 - 只有在泛型调用的时候,才给定该泛型实际的类型 @@ -2031,13 +2383,15 @@ let point3d: Point3d = {x: 1, y: 2, z: 3}; 匿名函数泛型:和匿名函数类似 泛型分类: + - 函数泛型 - 接口泛型 - 类泛型 - 类型别名泛型 ::: code-group -```typescript [类型变量] + +```typescript // 类型变量的使用 // 给identity添加了类型变量T,T帮助捕获用户传入的类型,后面就能够使用这个类型了,这个identity函数就是泛型 function identity (args: T): T { @@ -2046,14 +2400,14 @@ function identity (args: T): T { ``` -```typescript [泛型的使用] +```typescript // 1. 显式声明 let output = identity('mystring') // 2. 类型推断,若不能自动推断类型,必须显式声明 let output = identity('mystring') ``` -```typescript [匿名函数泛型] +```typescript // 即不使用接口或者类型别名的形式定义,而是直接定义 // 比如:(val: T[]) => T[] let getVal: (val: T[]) => T[] = info => { @@ -2076,16 +2430,18 @@ type GetVal = (val: T[]) => T[] 泛型类型:泛型函数的类型和非泛型函数的类型基本类似,只是加了一个类型参数;可以把泛型参数当作整个接口的一个参数,这样可以清楚知道具体是哪个泛型类型,并且锁定了内部使用的泛型类型 使用: + - 泛型类型可以定义泛型类和泛型接口,无法定义泛型枚举和泛型命名空间 泛型类:和泛型接口类似,泛型变量跟在类名后面 使用: + - 泛型类指的是实例部分的类型,因为构造函数传入的值,只能在实例中使用,通过类名调用静态成员获取不到这个类型 ::: code-group -```typescript [泛型变量的使用] +```typescript // 错误的使用 function identity (args: T): T { // Property 'length' does not exist on type 'T'. @@ -2105,7 +2461,7 @@ function identity (args: Array): Array { } ``` -```typescript [泛型类型及定义泛型接口] +```typescript // 泛型参数名可以使用任意的标识,只要数量上和使用方式上对应即可 function identity (args: U[]): U[] { return args @@ -2119,7 +2475,7 @@ interface GeneraticIdentityFn { let myIdentity: GeneraticIdentityFn = identity ``` -```typescript [泛型参数当作接口的一个参数] +```typescript interface GeneraticIdentityFn { (args: T[]): T[]; } @@ -2132,7 +2488,7 @@ function identity (args: U[]): U[] { let myIdentity: GeneraticIdentityFn = identity ``` -```typescript [泛型类] +```typescript class GenericNumber { zeroValue: T; add: (x: T, y: T) => T; @@ -2142,6 +2498,7 @@ let myGenericNumber = new GenericNumber(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function(x, y) { return x + y; }; ``` + ::: 泛型约束:定义一个接口来描述约束条件,让泛型继承(使用关键字extends)这个接口实现约束。在定义了约束的泛型之后,传入的值(以及泛型参数的默认类型)必须要兼容这个约束类型 @@ -2149,22 +2506,23 @@ myGenericNumber.add = function(x, y) { return x + y; }; 在泛型约束中使用类型参数:声明一个类型参数,其被另一个类型参数所约束 泛型约束的使用场景: -- 确保是否存在某属性,例如`T extends string[]`,之后就能够使用数组类型的方法(属性)了 -- 检查对象是否存在某key,例如`T extends keyof Person`,这时T的类型就是Person类型的属性key的联合类型 -泛型参数默认类型:可以为泛型的类型参数指定默认类型,当使用时未直接指定类型参数或者说从实际值无法推导类型时,默认类型就会起作用。默认类型的设置和普通函数的默认值相似,即`` -- 有默认类型的类型参数是可选的,即在设置成实际类型时不必传入类型参数,比如泛型`B`,使用时,可以直接使用`B`,也可使用其他类型,比如`B` +- 确保是否存在某属性,例如 `T extends string[]`,之后就能够使用数组类型的方法(属性)了 +- 检查对象是否存在某key,例如 `T extends keyof Person`,这时T的类型就是Person类型的属性key的联合类型 + +泛型参数默认类型:可以为泛型的类型参数指定默认类型,当使用时未直接指定类型参数或者说从实际值无法推导类型时,默认类型就会起作用。默认类型的设置和普通函数的默认值相似,即 `` + +- 有默认类型的类型参数是可选的,即在设置成实际类型时不必传入类型参数,比如泛型 `B`,使用时,可以直接使用 `B`,也可使用其他类型,比如 `B` - 必选的类型参数必须在可选类型参数之前 - 默认类型比如满足类型参数的约束 泛型条件类型:`T extends U ? X : Y`,尽管使用了extends,但是不一定要满足继承关系,只需要满足条件即可,通常会结合infer一起使用 -在泛型中使用类类型:类类型语法为`new (x: number) => Point`等同于`{ new (x: number): Point }`,表示返回一个包含类型为Point的构造函数的对象类型,默认类的构造函数类型为其本身 - +在泛型中使用类类型:类类型语法为 `new (x: number) => Point`等同于 `{ new (x: number): Point }`,表示返回一个包含类型为Point的构造函数的对象类型,默认类的构造函数类型为其本身 ::: code-group -```typescript [泛型约束用法] +```typescript interface Lengthwise { length: number; } @@ -2178,7 +2536,7 @@ function loggingIdentity(arg: T): T { loggingIdentity({length: 10, value: 3}); ``` -```typescript [在泛型约束中使用类型参数] +```typescript // 泛型K继承了泛型T的key的类型 function getProperty(obj: T, key: K) { return obj[key]; @@ -2190,7 +2548,7 @@ getProperty(x, "a"); // okay getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'. ``` -```typescript [在泛型约束中使用类类型] +```typescript function createInstance(c: new () => A): A { return new c() } @@ -2206,7 +2564,7 @@ class BeeKeeper { createInstance(Bee).Keeper.hasMask ``` -```typescript [泛型与构造函数结合使用1] +```typescript // 类实现接口时,应该分别定义接口的属性和构造函数类型(两者不能放在一个接口中) // 定义接口属性 interface Point { @@ -2244,7 +2602,7 @@ function newPoint ( let point: Point = newPoint(Point2D, 1, 2) ``` -```typescript [泛型与构造函数结合使用2] +```typescript // 泛型T不能用作值new T(),下面这个是错的 class GenericCreator { create() : T { @@ -2271,13 +2629,14 @@ let creator = new GenericCreator() let firstClass: FirstClass = creator.create(FirstClass) ``` + ::: 注意事项: ::: code-group -```typescript [ 泛型间赋值] +```typescript type A = { name: T; } @@ -2291,7 +2650,7 @@ type B = A let a: B; ``` -```typescript [泛型学习1] +```typescript type FC

= FunctionComponent

interface FunctionComponent

= { @@ -2302,11 +2661,11 @@ interface FunctionComponent

= { } ``` -```typescript [泛型嵌套] +```typescript type A = B>> ``` -```typescript [泛型递归] +```typescript // 单链表 type ListNode = { data: T; @@ -2320,7 +2679,7 @@ declare var HTMLElement: { } ``` -```typescript [递归调用] +```typescript // 将对象所有(包括嵌套)属性变为可选 type DeepPartial = T extends Function ? T @@ -2337,7 +2696,42 @@ type PartialWindow = DeepPartial 可迭代对象:实现了属性Symbol.iterator,比如Array、Map、Set、String、Int32Array、Unit32Array、arguments等 +```typescript +function toArray(xs: Iterable): X[] { + return [...xs] +} +``` + ### for...of和for...in - for...of:遍历可迭代对象,调用对象上的Symbol.iterator方法 - for...in:以任意顺序迭代一个对象除Symbol以外的可枚举的属性,包括继承的属性 + +```typescript +// 区别一:数组 +let list = [4, 5, 6] + +for(let i of list) { + // 获取对象的属性的值:4, 5, 6 + console.log(i) +} + +for (let i in list) { + // 获取对象的可枚举属性的键:0, 1, 2 + console.log(i) +} + +// 区别二:Set +let pets = new Set(['cat', 'dog', 'hamster']) +pets['species'] = 'mammals' + +for (let pet of pets) { + // 'cat', 'dog', 'hamster' + console.log(pet) +} + +for (let pet in pets) { + // 'species' + console.log(pet) +} +``` diff --git "a/docs/usage-frame/typescript/typescript\344\272\214\346\234\237\347\237\245\350\257\206\347\202\271.md" "b/docs/usage-frame/typescript/typescript\344\272\214\346\234\237\347\237\245\350\257\206\347\202\271.md" index 5392426..63441b4 100644 --- "a/docs/usage-frame/typescript/typescript\344\272\214\346\234\237\347\237\245\350\257\206\347\202\271.md" +++ "b/docs/usage-frame/typescript/typescript\344\272\214\346\234\237\347\237\245\350\257\206\347\202\271.md" @@ -1,7 +1,7 @@ # TypeScript 知识点(第二期) -> 参考文档:http://www.patrickzhong.com/TypeScript/ -> 注意:可能有些过时内容 +> 参考文档:http://www.patrickzhong.com/TypeScript/ +> 注意:可能有些过时内容 > 😢😢😢表示文中不明白的,未记录的内容 ## 声明合并 @@ -11,20 +11,21 @@ 声明定义:声明会创建三种实体的组合,分别是命名空间、类型或值,如下所示: | Declaration Type | Namespace | Type | Value | -| --- | --- | --- | --- | -| Namespace | ✔ | | ✔ | -| Class | | ✔ | ✔ | -| Enum | | ✔ | ✔ | -| Interface | | ✔ | | -| Type Alias | | ✔ | | -| Function | | | ✔ | -| Variable | | | ✔ | +| ---------------- | --------- | ---- | ----- | +| Namespace | ✔ | | ✔ | +| Class | | ✔ | ✔ | +| Enum | | ✔ | ✔ | +| Interface | | ✔ | | +| Type Alias | | ✔ | | +| Function | | | ✔ | +| Variable | | | ✔ | 声明合并分类: 1. 接口合并:把双方的成员放到一个同名的接口里 注意: + - 接口的非函数成员若不唯一,他们必须要是相同的类型,否则报错 - 接口的函数成员若有多个,则会当成该函数的重载 - 接口合并时,后者优先级更高(后者的成员放在合并接口的前面) @@ -33,18 +34,19 @@ 2. 命名空间合并:同名的命名空间也会合并其成员,命名空间会创建出命名空间和值 注意: + - 命名空间内模块导出的同名接口会进行合并 - 后面的命名空间的导出成员会加到第一个命名空间里面 - 命名空间的非导出成员仅在原有的命名空间内可见,其他命名空间合并过来的成员无法访问 3. 命名空间可以和类、函数、枚举类型合并,只要命名空间的定义符合将要合并类型的定义,合并结果包含两者的声明类型(可以使用这个实现JavaScript的设计模式) - 4. 命名空间与类合并:合并结果是一个类带有一个内部类,可以使用命名空间为类增加静态属性,类只能访问已导出的命名空间成员 5. 命名空间与函数合并:可以给函数添加一些属性,保证类型安全 6. 命名空间与枚举类型合并:扩展枚举类型,这里的扩展,更多的是对枚举类型的应用罢了 ::: code-group -```typescript [接口合并] + +```typescript interface Document { createElement (tagName: any): Element } @@ -66,7 +68,8 @@ interface Document { } ``` -```typescript [命名空间与类合并] + +```typescript class Album { // 使用命名空间中的类 label: Album.AlbumLabel @@ -75,7 +78,8 @@ namespace Album { export class AlbumLabel {} } ``` -```typescript [命名空间与函数合并] + +```typescript function buildLabel (name: string): string { // 使用命名空间中的属性 return buildLabel.prefix + name + buildLabel.suffix @@ -86,7 +90,8 @@ namespace buildLabel { } console.log(buildLabel('sam smith')) ``` -```typescript [命名空间扩展枚举类型] + +```typescript enum Color { red = 1, green = 2 @@ -102,22 +107,27 @@ namespace Color { // 使用,得出一个新的值罢了(所谓的扩展?😢😢😢) console.log(Color.mixColor('yellow')) ``` + ::: **声明合并注意事项**: + - 类不能与其他类或变量合并 ## 混入mixins 多继承的弊端: + - 在支持多继承的环境下,若一个子类所继承的多个父类都拥有一个同名方法,子类在调用该方法时,不知道调用哪一个父类的该方法 混入定义:有的时候,声明一个同时继承两个或多个类的类是一个好的想法,思路有2种: + - 定义两个基类,然后定义一个子类implements两个基类,然后定义一个函数,将基类的原型prototype的方法实现/复制到子类的原型prototype中 - 定义两个基类,然后定义一个子类,并定义一个同名子类的接口extendds两个基类,然后定义一个函数,将基类的原型prototype的方法实现/复制到子类的原型prototype中 ::: code-group -```typescript [implements] + +```typescript class Basic1 { isBasic1: boolean setBasic1 () { @@ -147,7 +157,8 @@ function mixins (child: any, basics: any[]) { mixins(Child, [Basic2, Basic1]) ``` -```typescript [extends] + +```typescript class Basic1 { isBasic1: boolean setBasic1 () { @@ -180,25 +191,29 @@ function mixins (child: any, basics: any[]) { mixins(Child, [Basic2, Basic1]) ``` -::: +::: 注意事项: + - 若多个基类包含了同名方法,只会继承传入*复制函数*里最后一个基类赋值的该方法 + ### implements与extends的区别 类的声明:既可以当成一个*类*来使用,同时也能当成一个*类类型*来使用;因为类可以作为一个接口使用,同时类实现了接口,所以有以下关系: - - 类只能继承类 - - 接口能继承类和接口 - - 类能实现接口和类,因为接口不包含具体实现呀,所以只能在类上实现 - - 接口不能实现接口或类 + +- 类只能继承类 +- 接口能继承类和接口 +- 类能实现接口和类,因为接口不包含具体实现,所以只能在类上实现 +- 接口不能实现接口或类 implements:一个新的类,从父类或接口实现所有的属性和方法,同时可以重写属性和方法,还可以新增一些属性和方法 extends:一个新的接口或类,从父类或者接口继承所有的属性和方法,不能重写属性,可以重写方法 ::: code-group -```typescript [类声明] + +```typescript // 声明类Point,同时也声明了一个类类型Point class Point { x: number @@ -232,42 +247,53 @@ printPoint(new Point(2, 3)) ## 三斜线指令 定义: + - 三斜线指令是包含单个XML标签的单行注释,注释的内容会被作为编译器指令使用 - 仅可放在包含它的文件的最顶端,其前面只能出现单行、多行注释(包括三斜线指令);放在其他的地方只会被当作普通单行注释 分类: -1. 三斜线引用`/// `: - - 用于声明文件间的依赖,告诉编译器在编译过程中要引入的额外的文件; - - 在一个文件加入到`file`字段列表里时,它包含的所有三斜线引用都要被处理,以他们在文件里出现的顺序,使用深度优先的方式进行解析 - - 三斜线引用的路径path是相对于包含它的文件的 - - 引入不存在的path或自身会报错 - - 当使用了`--noResolve`时,三斜线引用会被忽略 -2. 三斜线类型引用`/// ` - - 用于声明对某个包的依赖,对这些包名的解析与在import语句里对模块名的解析类似,可以看成是import声明的包 + +1. 三斜线引用 `/// `: + +- 用于声明文件间的依赖,告诉编译器在编译过程中要引入的额外的文件; +- 在一个文件加入到 `file`字段列表里时,它包含的所有三斜线引用都要被处理,以他们在文件里出现的顺序,使用深度优先的方式进行解析 +- 三斜线引用的路径path是相对于包含它的文件的 +- 引入不存在的path或自身会报错 +- 当使用了 `--noResolve`时,三斜线引用会被忽略 + +2. 三斜线类型引用 `/// ` + +- 用于声明对某个包的依赖,对这些包名的解析与在import语句里对模块名的解析类似,可以看成是import声明的包 + 3. `/// `: - - 将当前文件标记为一个默认库,告诉编译器在编译过程中不要包含这个默认库,与在命令行中使用--noLib类似 - - 当传递了--skipDefaultLibCheck时,编译器指挥忽略检查带有该标记的文件 -4. `/// `: - - 默认情况下生成的amd模块是匿名的,该指令允许给编译器传入一个可选的模块名 - - 这会将NamedModule传入到amd define函数里面 + +- 将当前文件标记为一个默认库,告诉编译器在编译过程中不要包含这个默认库,与在命令行中使用--noLib类似 +- 当传递了--skipDefaultLibCheck时,编译器指挥忽略检查带有该标记的文件 + +4. `/// `: + +- 默认情况下生成的amd模块是匿名的,该指令允许给编译器传入一个可选的模块名 +- 这会将NamedModule传入到amd define函数里面 ## 模块 > 内部模块称为命名空间;外部模块称为模块; 定义: + - 模块在自身的作用域(而非全局)内执行 - 模块内的变量、函数、类仅在模块内使用,除非使用export等形式进行导出操作 - 模块是自声明的,两模块之间的关系使用import和export建立 -- 模块使用`模块加载器`去导入其他的模块 +- 模块使用 `模块加载器`去导入其他的模块 模块加载器:在执行此模块代码前去查找并执行这个模块的所有依赖,其分类有: + - 作用于commonjs模块的nodejs加载器 - 作用域amd模块的requirejs加载器 ### 导出 -声明的导出:任何(变量、函数、类、类型别名、接口)*声明*都能够通过添加export关键字来导出 +声明的导出:任何(变量、函数、类、类型别名、接口)*声明*都能够通过添加export关键字来导出 ```typescript // 变量声明 @@ -312,7 +338,8 @@ import './module-path' ### 默认导入导出 默认导出定义: -- 默认导出的语法为`export default xxx` + +- 默认导出的语法为 `export default xxx` - 一个模块只能够有一个default导出,且导入默认导出的方式也和普通导出不一样 - 类和函数的声明可以直接被标记为默认导出,此时他们的名字可省略 - 默认导出部分xxx也可以是一个值 @@ -339,20 +366,23 @@ import vclass from 'modult-path' ### 模块的动态加载 定义: + - 语法:`import id = require('xxx')` - 只想在某种条件下才加载某个模块,可直接调用模块加载器并保证类型完全 - 模块加载器会通过requie被动态调用,只在被需要时加载 - 为了确保类型安全,可以使用typeof在表示类型的地方使用,用于表示模块的类型 ::: code-group -```typescript [NodeJS动态模块加载] + +```typescript declare function require (moduleName: string): any import { Zip } from './zip' if (xxx) { let validator: typeof Zip = require('./zip') } ``` -```typescript [RequireJS动态模块加载] + +```typescript declare function require (moduleNames: string[], onLoad: (...args: any[]) => void): void import * as Zip from './zip' if (xxx) { @@ -361,7 +391,8 @@ if (xxx) { }) } ``` -```typescript [SystemJS动态模块加载] + +```typescript declare const System: any import { Zip } from './zip' if (xxx) { @@ -370,21 +401,24 @@ if (xxx) { }) } ``` + ::: ### JavaScript库的使用 定义: + - 若想描述非typescript编写的类库的类型,需要声明类库所暴露出的API(即在.d.ts文件中定义) -- 为模块定义一个.d.ts文件,使用module关键字并把模块的名字括起来,方便后面使用`/// node.d.ts`以及导入语句加载模块 +- 为模块定义一个.d.ts文件,使用module关键字并把模块的名字括起来,方便后面使用 `/// node.d.ts`以及导入语句加载模块 - 若不想在使用新模块之前去写该模块的声明,可以采用声明的简写形式以便快速使用该模块 模块声明通配符: + - 某些模块(systemjs、amd)支持导入非JavaScript内容,通常会使用一个前缀、后缀来表示特殊的加载语法,这时可以通过通配符表示这些情况 umd模块: -- 有些模块被设计成兼容多个模块加载器或者不适应模块加载器,这些模块可以通过导入或全局变量的形式访问 +- 有些模块被设计成兼容多个模块加载器或者不适应模块加载器,这些模块可以通过导入或全局变量的形式访问 ```typescript // 模块的声明 @@ -419,15 +453,14 @@ mathLib.isPrime(2) mathLib.isPrime(2) ``` - ### 模块导出注意事项 1. 尽可能在顶层导出内容,而不是嵌套过多的层次,比如不要在模块中导出一个命名空间(多余)、不要导出类的静态方法(类本身就增加了嵌套) 2. 若模块的功能明确,就是导出特定内容,应该设置一个默认导出export default 3. 当要导出大量内容的时候,导入时,可以使用通配符结合别名导入所有内容 4. 当扩展模块的功能时,不应该改变原来的对象,而是导出一个新的实体(例如继承)提供新的功能 -5. 模块中不应该使用命名空间导出,比如`export namespace Foo {}` -6. 对于模块的引用应当使用import,而非使用`///` +5. 模块中不应该使用命名空间导出,比如 `export namespace Foo {}` +6. 对于模块的引用应当使用import,而非使用 `///` ```typescript // 模块扩展 @@ -453,11 +486,13 @@ export { test } from './cal.ts' 定义:顾名思义,即扩展模块,给模块增加新的特性或功能 注意事项: + - 仅可以对模块中已经存在的声明进行扩展 - 不能扩展模块的默认导出 ::: code-group -```typescript [模块扩展] + +```typescript // obse.ts export class Obse {} @@ -477,7 +512,8 @@ import './map.ts' let o: Obse o.map(x => x.toFixed()) ``` -```typescript [全局扩展] + +```typescript // obse.ts export class Obse {} declare global { @@ -488,11 +524,13 @@ declare global { } Array.property.toObse = function () {} ``` + ::: ## 模块解析 定义: + - 指编译器在查找导入模块内容时所遵循的流程 - 编译器需要准确的知道它表示什么,并且需要检查它的定义 - 编译器会使用某种策略(classic、node)定位表示导入模块的文件,当解析失败且模块名是非相对的,编译器会尝试定位一个外部模块声明,否则会记录一个错误 @@ -501,29 +539,32 @@ Array.property.toObse = function () {} **classic**:typescript以前的默认解析策略,当前主要用向后兼容 -对于相对导入的模块,查找流程为:相对路径,且查找顺序是先`.ts`, 后`.d.ts`;对于非相对导入的模块,查找流程为:从包含导入文件的目录依次向上级目录遍历,且查找顺序是先`.ts`, 后`.d.ts` +对于相对导入的模块,查找流程为:相对路径,且查找顺序是先 `.ts`, 后 `.d.ts`;对于非相对导入的模块,查找流程为:从包含导入文件的目录依次向上级目录遍历,且查找顺序是先 `.ts`, 后 `.d.ts` **node**:运行时模仿nodejs模块的解析机制 -对于相对路径导入的,例如`require('./moduleb')`,解析顺序为,直到有一个匹配上为止: -1. 检查`/root/src/moduleb.js`是否存在 -2. 检查`/root/src/moduleb/package.json`是否存在,且该文件是否指定了main模块,若有,则会引用该main模块 -3. 检查`/root/src/moduleb/index.js`是否存在 +对于相对路径导入的,例如 `require('./moduleb')`,解析顺序为,直到有一个匹配上为止: + +1. 检查 `/root/src/moduleb.js`是否存在 +2. 检查 `/root/src/moduleb/package.json`是否存在,且该文件是否指定了main模块,若有,则会引用该main模块 +3. 检查 `/root/src/moduleb/index.js`是否存在 + +对于非相对模块名的解析,例如 `require('moduleb')`会在node_modules查找该模块,从包含导入文件的模块依次向上级目录遍历,直到有一个匹配上为止: -对于非相对模块名的解析,例如`require('moduleb')`会在node_modules查找该模块,从包含导入文件的模块依次向上级目录遍历,直到有一个匹配上为止: 1. `/root/src/node_modules/moduleb.js` 2. `/root/src/mode_modules/moduleb/package.json`,若有main属性的话 3. `/root/src/node_modules/moduleb/index.js` -4. 然后是root目录,重复1-3,然后是根目录`/` +4. 然后是root目录,重复1-3,然后是根目录 `/` **typescript模块解析策略**:在上面两种类型(.ts, .d.ts)的基础上,增加了typescript源文件的扩展类型(.ts, .tsx, .d.ts),其解析顺序为: -1. `moduleb.ts`, -2. `moduleb.tsx`, -3. `moduleb.d.ts`, -4. `moduleb/package.json`, + +1. `moduleb.ts`, +2. `moduleb.tsx`, +3. `moduleb.d.ts`, +4. `moduleb/package.json`, 5. `@types/moduleb.d.ts`(这个是针对非相对导入的模块) -6. `moduleb/index.ts`, -7. `moduleb/index.tsx`, +6. `moduleb/index.ts`, +7. `moduleb/index.tsx`, 8. `moduleb/index.d.ts` ### 附加的模块解析标记 @@ -531,14 +572,17 @@ Array.property.toObse = function () {} 编译器*有一些额外的标记*用来通知编译器在源码编译成最终输出的过程中都发生了哪个转换 **baseUrl**: + - 要求在运行时模块都被放到了一个文件夹里,这些模块的源码可以在不同的目录下,但是构建脚本会将他们集中到一起 - 设置baseUrl告诉编译器到哪里查找模块,所有非相对模块导入都会被当作相对于baseUrl baseUrl的值(只对非相对模块的导入有效): + - 命令行中baseUrl的值,若给定的路径是相对的,则将相对于当前路径进行计算 - tsconfig.json的baseUrl,若给定的路径是相对的,则相对于该文件进行计算 **路径映射**: + - 加载器使用映射配置将模块名映射到运行时的文件 - 通过使用tsconfig.json的paths支持这样的声明映射 - paths可以指定复杂的映射,包括指定多个回退位置(比如将多处位置的合并集中到一处) @@ -567,6 +611,7 @@ baseUrl的值(只对非相对模块的导入有效): ``` **利用rootDirs指定虚拟目录**: + - 使用整个字段,可以告诉编译器生成整个虚拟目录的roots,因此编译器可以在虚拟目录下解析相对模块导入,就好像他们被合并在一起一样 - 当编译器在某一rootDirs的子目录下发现了相对模块导入时,会尝试从每一个rootDirs中导入,不论其是否存在 @@ -586,10 +631,12 @@ baseUrl的值(只对非相对模块的导入有效): **跟踪模块解析:** 定义: + - 编译器在解析模块时可能访问当前文件夹外的文件,会导致很难诊断模块为什么没有被解析或者解析到了错误位置 -- 通过`--traceResolution`启用编译器的模块解析跟踪,会告诉我们在模块解析过程中发生了什么 +- 通过 `--traceResolution`启用编译器的模块解析跟踪,会告诉我们在模块解析过程中发生了什么 输出结果: + ```bash # 导入的名字typescript,位置src/app.ts ======== Resolving module 'typescript' from 'src/app.ts'. ======== @@ -618,17 +665,20 @@ File 'node_modules/typescript/lib/typescript.d.ts' exist - use it as a module re ## 命名空间 定义: + - 需要一种手段来组织代码,便于在记录他们类型的同时还不用担心与其他对象产生命名冲突,这时可以使用命名空间,将这些代码放在该命名空间下 - 主要是用于提供逻辑分组和避免命名冲突 -用法:`namespace namespaceName { export let a = 1 }`,使用`let kk = namespaceName.a` +用法:`namespace namespaceName { export let a = 1 }`,使用 `let kk = namespaceName.a` 命名空间的分离: + - 当应用越来越大时,需要将代码分离到不同的文件中进行维护 -- 虽然处于不同的文件,但是仍然属于同一个命名空间(命名空间名一样),在使用的时候就像在一个文件中定义的一样,只不过需要使用`引用标签`告诉编译器文件之间的关联 +- 虽然处于不同的文件,但是仍然属于同一个命名空间(命名空间名一样),在使用的时候就像在一个文件中定义的一样,只不过需要使用 `引用标签`告诉编译器文件之间的关联 命名空间的别名: -- 简化命名空间操作的方法是使用`import q = namespaceName.exportVar`的方式给命名空间内部的变量起一个短的名字 + +- 简化命名空间操作的方法是使用 `import q = namespaceName.exportVar`的方式给命名空间内部的变量起一个短的名字 - 这个操作是创建一个别名,且q会生成与原始符号namespaceName.exportVar不同的引用,两者之间互不影响 ```typescript @@ -665,4 +715,4 @@ namespace Validation { /// let a = new Letter() let b = new Upper() -``` \ No newline at end of file +```