From 7c1fac9e3bd6a0e2c81e54f05fbfdb0c0095d04b Mon Sep 17 00:00:00 2001 From: lucifer Date: Wed, 29 Apr 2020 10:52:31 +0800 Subject: [PATCH 01/13] fix: typo --- docs/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/README.md b/docs/README.md index cd04d31..fc045af 100644 --- a/docs/README.md +++ b/docs/README.md @@ -174,7 +174,7 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, - [原型和继承](./topics/js/prototype.md) - [this](./topics/js/this.md) + 如果上面的专题你都看过了,那么来回答几个问题看你是否真的掌握了。 @@ -310,11 +310,11 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, - [策略模式](./topics/design-pattern/strategy.md) - [代理模式](./topics/design-pattern/proxy.md) - [观察者模式](./topics/design-pattern/observer.md) - + + + + + ### 框架 🖼️ @@ -853,9 +853,9 @@ Flutter 可以与现有的代码一起工作。在全世界,Flutter 正在被 - [Java 基础学习(廖雪峰)](https://www.liaoxuefeng.com/wiki/1252599548343744) - [Python 开发基础 (微软出品)](https://github.com/microsoft/c9-python-getting-started) - [Go 语言圣经(英文版)](http://www.gopl.io/) - > [Go 语言圣经(中文版)](http://shouce.jb51.net/gopl-zh/) - +- [Go 语言圣经(中文版)](http://shouce.jb51.net/gopl-zh/) + + 上面的东西选择性掌握即可,这可以说是加分项,一般不会要求这些都掌握的。 From badf2235af28d8d0479014d0e6498d6a20a091c2 Mon Sep 17 00:00:00 2001 From: lucifer Date: Wed, 29 Apr 2020 10:55:15 +0800 Subject: [PATCH 02/13] =?UTF-8?q?feat:=20=E3=80=8A=E4=B8=80=E6=96=87?= =?UTF-8?q?=E7=9C=8B=E6=87=82=E6=B5=8F=E8=A7=88=E5=99=A8=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E5=BE=AA=E7=8E=AF=E3=80=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/README.md b/docs/README.md index fc045af..8582a0a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -366,6 +366,8 @@ Redux 中核心就是一个单一的 state。state 通过闭包的形式存放 - [事件模型](./topics/browser/event.md) +- [一文看懂浏览器事件循环](https://lucifer.ren/blog/2019/12/11/event-loop/) + - 浏览器安全策略 - 事件循环 From 01cbaed3d250c8f118986fd7c25de3701275d328 Mon Sep 17 00:00:00 2001 From: lucifer Date: Sat, 16 May 2020 17:15:50 +0800 Subject: [PATCH 03/13] Update issue templates --- .github/ISSUE_TEMPLATE/----.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/----.md diff --git a/.github/ISSUE_TEMPLATE/----.md b/.github/ISSUE_TEMPLATE/----.md new file mode 100644 index 0000000..6eda3a2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/----.md @@ -0,0 +1,10 @@ +--- +name: 每日一题 +about: 每日一题模板 +title: "【每日一题】- 2020-04-09 - xxxxx" +labels: Daily Question +assignees: '' + +--- + + From 776a40e0fcbe1e87a65222346d6f1f9b3f4e012f Mon Sep 17 00:00:00 2001 From: lucifer Date: Fri, 6 Nov 2020 11:55:46 +0800 Subject: [PATCH 04/13] Update README.md --- docs/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/README.md b/docs/README.md index 8582a0a..f247544 100644 --- a/docs/README.md +++ b/docs/README.md @@ -189,6 +189,7 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, - 原型链能够实现所谓的继承的本质原因是什么? - 箭头函数是用来解决什么问题的? - 什么是高阶函数?用处和用法? +- 什么是异步编程,为什么说它对Web开发很重要? ### 编程题 ✍️ @@ -538,6 +539,13 @@ Promise 这种模式。 使用起来就好像“浏览器的原生 API”一样 施工中 +如果上面的专题你都看过了,那么来回答几个问题看你是否真的掌握了。 + +- 你知道哪些 编程范型,它们对 JavaScript开发者来有什么用? +- 什么是函数式编程? +- 面向对象的核心是什么?传统类继承和原型继承的区别在哪里? +- 函数式编程(FP)和面向对象编程(OO)各自优点和不足是什么? + ### 状态管理 状态管理这个东西在 React 和 Vue 这种视图框架大规模出现之后才出现的东西, @@ -551,6 +559,10 @@ Promise 这种模式。 使用起来就好像“浏览器的原生 API”一样 - GraphQL 可以当作状态管理框架使用么? - 我为什么要用状态管理框架,直接存到全局不香么? +如果上面的专题你都看过了,那么来回答几个问题看你是否真的掌握了。 + +- 什么是双向绑定和单向数据流,它们有什么不同? + ### 项目经验 project @@ -816,6 +828,10 @@ Flutter 可以与现有的代码一起工作。在全世界,Flutter 正在被 这里后续会系统性列举一些微前端的资料。 +如果上面的专题你都看过了,那么来回答几个问题看你是否真的掌握了。 + +- 单体架构和微构架的优点和不足是什么? + ### 编译/转义 From 1ec6719d01f8625618bf0de27e5a585f343087ad Mon Sep 17 00:00:00 2001 From: lucifer Date: Fri, 6 Nov 2020 12:35:44 +0800 Subject: [PATCH 05/13] =?UTF-8?q?feat:=20=E4=B8=80=E5=A4=A7=E6=B3=A2?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .docsifytopdfrc.js | 9 + docs/README.md | 161 +-- docs/topics/ts/leetcode-interview-ts.md | 341 ++++++ docs/topics/ts/ts-config.md | 362 ++++++ docs/topics/ts/ts-exercises-2.md | 1498 +++++++++++++++++++++++ docs/topics/ts/ts-exercises.md | 1031 ++++++++++++++++ docs/topics/ts/ts-generics.md | 758 ++++++++++++ docs/topics/ts/ts-internal.md | 140 +++ docs/topics/ts/ts-type-system.md | 318 +++++ docs/topics/ts/ts-type.md | 140 +++ pdf/readme.pdf | Bin 0 -> 13991 bytes 11 files changed, 4678 insertions(+), 80 deletions(-) create mode 100644 .docsifytopdfrc.js create mode 100644 docs/topics/ts/leetcode-interview-ts.md create mode 100644 docs/topics/ts/ts-config.md create mode 100644 docs/topics/ts/ts-exercises-2.md create mode 100644 docs/topics/ts/ts-exercises.md create mode 100644 docs/topics/ts/ts-generics.md create mode 100644 docs/topics/ts/ts-internal.md create mode 100644 docs/topics/ts/ts-type-system.md create mode 100644 docs/topics/ts/ts-type.md create mode 100644 pdf/readme.pdf diff --git a/.docsifytopdfrc.js b/.docsifytopdfrc.js new file mode 100644 index 0000000..9081804 --- /dev/null +++ b/.docsifytopdfrc.js @@ -0,0 +1,9 @@ +module.exports = { + contents: ["./docs/_sidebar.md"], // array of "table of contents" files path + pathToPublic: "pdf/readme.pdf", // path where pdf will stored + mainMdFilename: "README.md", + pathToStatic: "./docs", + pathToDocsifyEntryPoint: "./docs", + removeTemp: true, // remove generated .md and .html or not + emulateMedia: "screen", // mediaType, emulating by puppeteer for rendering pdf, 'print' by default (reference: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pageemulatemediamediatype) +}; diff --git a/docs/README.md b/docs/README.md index 8582a0a..5cf2b79 100644 --- a/docs/README.md +++ b/docs/README.md @@ -135,8 +135,14 @@ Job Model 是 一个很重要的参考标准。 当然每个公司的 Job Model 其实我自己也维护了一个个人简历的项目,只不过目前只是用来做简历信息同步,没有做深层次的内容, 我的 github resume 地址:https://github.com/azl397985856/resume -我的个人网站地址: https://lucifer.ren/ , 我并没有在个人网站放很多文章,根据个人喜好来吧, -如果你想做的,把个人网站做成主库,然后写文章同步各大平台也是一个非常棒的做法。 +我的个人博客网站: https://lucifer.ren/blog + +下面我列举几个我见到的简历上的减分项目,大家可以自己对号入座一下: + +- 不要全部都是名词的罗列,要突出你利用你提到的技术名词作了什么 +- 少写和岗位无关的内容,比如参加公益活动, 喜欢打篮球等 +- 如果形象好的可以考虑贴个照片,增加印象分,照片要干净利落。 +- 不要写和自己工作经验不符的内容,比如工作五年了,简历还都是熟悉 HTML,CSS 这种东西,应该多写和自己职级匹配的东西,多写对方希望什么样的人的特质信息。关于这个可以参考我上面的 JOB Model。 ### 自我介绍 @@ -150,9 +156,7 @@ Job Model 是 一个很重要的参考标准。 当然每个公司的 Job Model 自我介绍最好不是简历的复述,这样会给人不太好的感觉。但是即使是复述,如果 做到熟练和清晰也是不错的, 因为很多面试官在面试之前根本没有看你的简历。 -> 草稿 - -[如何做自我介绍](./topics/introduction/intro.md) +- [如何做自我介绍](./topics/introduction/intro.md) ### JavaScript 🗒️ @@ -162,9 +166,7 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, > 在学习接下来的东西之前,建议大家先打好基础,这里只推荐两本书,一本是《You-Dont-Know-JS》,另一本是《JavaScript: The Good Parts》。 -我非常不建议你没有系统学习 JS 之前就去刷题目,这是毫无意义的,根本无法从根本上理解。 -之后对前端技能的考察会越来越严格。大家系统性学习之后,推荐过来看一下我这里总结的东西, -最后去网上找一些经典的题目,通过这些题目来检查自己,而不是一开始就去网上找题目做。 +我非常不建议你没有系统学习 JS 之前就去刷题目,这是毫无意义的,根本无法从根本上理解。之后对前端技能的考察会越来越严格。大家系统性学习之后,推荐过来看一下我这里总结的东西,最后去网上找一些经典的题目,通过这些题目来检查自己,而不是一开始就去网上找题目做。 这里列举了几个我觉得比较有代表且比较有意思的主题: @@ -192,23 +194,15 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, ### 编程题 ✍️ -算法分为三部分: - -- 第一部分是 leetcode 上的题目。 - -- 第二部分是手写题目,实现一个小功能。比如实现 bind, curry 等 - -- 第三部分是 leetcode 题目变种 +算法分为两种:一种是算法题,比如 leetcode 上的那种或者其变形题。另一种是手写题, 比如让你手写 bind 函数等。 -本仓库只列举后两个部分,对于第一部分可以去我的另一个仓库 - [leetcode 题解](https://github.com/azl397985856/leetcode) 查看 +关于算法题,大家可以去我的 [leetcode 题解](https://github.com/azl397985856/leetcode) 查看,手把手教你刷算法。 另外对于不同的阶段,我们应该采取不同的刷题策略。 1. 初级阶段 -看一些基础内容,比如数据结构和算法的基本知识,看一些 JS 语言基础的一些东西。 - -如果需要刷题的话,一定要从简单开始。 +看一些基础内容,比如数据结构和算法的基本知识,看一些 JS 语言基础的一些东西。如果需要刷题的话,一定要从简单开始。 2. 中级 @@ -224,13 +218,14 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, > ℹ️ 以下这些题目都是自己经历或者从网上整理下来的,之后还会继续同步更新。 -对于编程题,可能会让你白板写,也可能让你用 Online Editor。 -因此我的建议,是掌握白板写,熟练至少一种 Online Editor, -这样可以在适当时候要求面试官让你用你熟悉的 Online Editor 书写。 +对于编程题,可能会让你白板写,也可能让你用 Online Editor。因此我的建议,是掌握白板写,熟练至少一种 Online Editor,这样可以在适当时候要求面试官让你用你熟悉的 Online Editor 书写。 + 比较有名的 Online Editor 有 JSBin , CodePen, StackBlitz 等,我个人比较推荐 [StackBlitz](https://stackblitz.com/) > Tips: 如果不是白板写,一定要注意调试,即使没有做出来,但是良好的调试习惯和技能也能加分。 +以下是我自己实现的一些经典的手写题,算是比较全了。把这些手写题刷一下, 再跟着我刷 leetcode 基本编程题是拿下了。 + - [大数相加](./topics/algorthimn/bigNumberSum.md) - [手写 bind](./topics/algorthimn/bind.md) - [实现加法](./topics/algorthimn/bitTwoSum.md) @@ -267,11 +262,29 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, - [获取页面所有的 tagname](./topics/algorthimn/getAllHTMLTags.md) - [实现 XPath](./topics/algorthimn/xpath.md) +### 浏览器 + +前端和浏览器是分不开的,关于 JS 的宿主环境的理解的重要性是不亚于框架。关于这个话题,我总结了几篇文章给大家。 + +浏览器 + +- [事件模型](./topics/browser/event.md) + +- [一文看懂浏览器事件循环](https://lucifer.ren/blog/2019/12/11/event-loop/) + +- 浏览器安全策略 + +- 事件循环 + +- BOM API + +- Chrome 浏览器中的进程和线程 + ### CSS 🦋 国外会有一些类似 `CSS/HTML 专家` 的岗位,可以看出这部分内容还是相对比较重要且难以精通的。 -这里我推荐 medium 社区的,来自 Elad Shechter 的关于 CSS 架构系列文章: +我本人对 CSS 不是很精通, 这里给几个资料吧。这里我推荐 medium 社区的,来自 Elad Shechter 的关于 CSS 架构系列文章: - [Normalize CSS or CSS Reset?!](https://medium.com/@elad/normalize-css-or-css-reset-9d75175c5d1e) - [CSS Architecture — Folders & Files Structure](https://medium.com/@elad/css-architecture-folders-files-structure-f92b40c78d0b) @@ -288,9 +301,11 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, ![设计题](./assets/imgs/topics/design/design-cover.jpg) -这类题目有时候是给一个情景,有时候是直接让你实现一个轮子,答案也往往是开放式的。 -需要你对组件和代码设计有一定的基础。这部分主要考察候选人综合实力,思维开放性, -思维严密性,做事的方式等。 +这类题目有时候是给一个情景,有时候是直接让你实现一个轮子,答案也往往是开放式的。需要你对组件和代码设计有一定的基础。这部分主要考察候选人综合实力,思维开放性,思维严密性,做事的方式等。 + +这种题目没有很好的应对方式, 唯有自己平时写代码的时候多思考,多重构,并且要充分思考之后再行动,这样才能慢慢锻炼。能力有了,和面试官的互动也是很重要的。 + +下面我列举了几个我被面试过的几个设计题供大家参考。 - [大量数据滚动加载](./topics/design/lazy-scroll.md) - [如何设计一个实时检查更新的功能](./topics/design/auto-update.md) @@ -306,10 +321,14 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, 这里有一份英文系列文章 [《JavaScript Design Patterns》](https://wanago.io/2019/11/11/javascript-design-patterns-1-singleton-and-the-module/),文章的特点是不仅用 JS 还使用 TS 进行了对比讲解,另外紧跟最新技术,比如讲解外观模式的时候就是拿`React Hooks`重构举的例子。 +实际上设计模式就是对大家经常碰到的问题进行了最佳实践的总结,并给它起了一个方便记忆的名字。比如我们玩 LOL(一个 5 V 5 游戏),遇到僵局,可以采用四一分推的战术, 队友一说四一分推,大家立马懂了,开始行动起来, 这本质和设计模式没有大的不同。 + +以下是一些非常常见的设计模式。 + - [单例模式](./topics/design-pattern/singleton.md) - [策略模式](./topics/design-pattern/strategy.md) - [代理模式](./topics/design-pattern/proxy.md) -- [观察者模式](./topics/design-pattern/observer.md) +- [观察者模式](./topics/design-pattern/observe.md) @@ -322,17 +341,17 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, 流行的框架当然也是兵家必争之地,如果你能够完全了解大型知名开源框架的代码和架构实现,那绝对是一个加分项。 -框架是为了解决特定问题才出现的,脱离实际业务谈框架选型以及优劣都是耍流氓。我们需要了解到各个框架在 -什么情况下产生的,他们是为了解决什么问题,适合的场景是什么样的,有什么不足等。只有对这些 -都非常熟悉,才能够在业务中作出合理的取舍,才能赢得面试官的认可。 +框架是为了解决特定问题才出现的,脱离实际业务谈框架选型以及优劣都是耍流氓。我们需要了解到各个框架在什么情况下产生的,他们是为了解决什么问题,适合的场景是什么样的,有什么不足等。只有对这些都非常熟悉,才能够在业务中作出合理的取舍,才能赢得面试官的认可。 + +我不推荐大家没搞懂 JS 等基础就开始研究框架。大家一定先打牢基础再来搞框架。 + +这里介绍两个几乎面试必问的两个框架 React 和 Vue,以及其周边。 #### React -React 考察的点就那么几点,从简单的生命周期,特定 API 的使用。 到 SetState 的原理, -虚拟 DOM,以及 DOM diff 算法等。 这部分需要大家对 React 有系统性认识。 +React 考察的点就那么几点,从简单的生命周期,特定 API 的使用。 到 SetState 的原理,虚拟 DOM,以及 DOM diff 算法等。 这部分需要大家对 React 有系统性认识。 -如果你想系统性学习 React,推荐看官网。 -除了官网,我这里推荐一份资料 - [全面介绍 React](https://jscomplete.com/learn/complete-intro-react#managing-side-effects) +如果你想系统性学习 React,推荐看官网。除了官网,我这里推荐一份资料 - [全面介绍 React](https://jscomplete.com/learn/complete-intro-react#managing-side-effects) 这部分其实可以参考我之前开的一个仓库 [从零开始开发一个 React](https://github.com/azl397985856/mono-react) @@ -341,6 +360,7 @@ React 考察的点就那么几点,从简单的生命周期,特定 API 的使 如果上面的专题你都看过了,那么来回答几个问题看你是否真的掌握了。 - React 的虚拟 DOM diff 算法一定比直接操作 DOM 快么?为什么? +- React 虚拟 DOM diff 的算法时间复杂度是多少?为什么? #### Redux @@ -354,27 +374,11 @@ Redux 中核心就是一个单一的 state。state 通过闭包的形式存放 #### Vue -> vue 部分我建议等到 vue 更新 3.0 之后再去研究 ta。 +> TODO #### Vuex -> vuex 部分我建议等到 vue 更新 3.0 之后再去研究 ta。 - -### 浏览器 - -浏览器 - -- [事件模型](./topics/browser/event.md) - -- [一文看懂浏览器事件循环](https://lucifer.ren/blog/2019/12/11/event-loop/) - -- 浏览器安全策略 - -- 事件循环 - -- BOM API - -- Chrome 浏览器中的进程和线程 +> TODO ### 小程序 @@ -392,20 +396,16 @@ Redux 中核心就是一个单一的 state。state 通过闭包的形式存放 ### 原生通信 -如果你做过混合式开发的话,原生通信一定是不能绕过的点。 -很多时候我们使用的都是封装好的方法,我们可以直接调用,甚至支持 -Promise 这种模式。 使用起来就好像“浏览器的原生 API”一样方便。 +如果你做过混合式开发的话,原生通信一定是不能绕过的点。很多时候我们使用的都是封装好的方法,我们可以直接调用,甚至支持 Promise 这种模式。 使用起来就好像“浏览器的原生 API”一样方便。 这从某种程度上来说,扩展了浏览器的功能。 - [h5 与原生 app 交互的原理](https://segmentfault.com/a/1190000016759517) ### 网络 -网络这部分虽然不需要我们像`网络工程师`一样熟悉很多底层细节, -但是我们至少需要有一个高层次的抽象的思维来看待网络这个世界, -从而帮助我们更好地理解它,并且利用它去解决一些问题,典型的就是性能优化, -其实线上定位问题等有时候也需要你懂一点网络知识。 -作为面试,可能需要你准备得更为深入一点。 +网络这部分虽然不需要我们像`网络工程师`一样熟悉很多底层细节,但是我们至少需要有一个高层次的抽象的思维来看待网络这个世界,从而帮助我们更好地理解它,并且利用它去解决一些问题,典型的就是性能优化, + +其实线上定位问题等有时候也需要你懂一点网络知识。作为面试,可能需要你准备得更为深入一点。 对于网络这部分,最重要的是要有一个大的概念,下面也会介绍。 @@ -445,8 +445,6 @@ Promise 这种模式。 使用起来就好像“浏览器的原生 API”一样 node -> 预计 11 月份开始整理 - 如果你要做全栈或者后端,那么 node 是一个相对平滑的选择 @@ -695,9 +702,7 @@ linter 是为了帮我我们找出 bug 而存在的,不要过分高估它的 #### 任务管理 -在 npm 出现之前,做任务管理的方式主要是自己处理或者记住第三方库,比如`grunt`,但是 npm 出现之后, -大家发现其实`npm script` + `构建工具`就可以解决前端绝大多数问题了。包括 VSCODE 中的任务管理,其实 -都有和 npm 有着很好的集成。 +在 npm 出现之前,做任务管理的方式主要是自己处理或者记住第三方库,比如`grunt`,但是 npm 出现之后,大家发现其实`npm script` + `构建工具`就可以解决前端绝大多数问题了。包括 VSCODE 中的任务管理,其实都有和 npm 有着很好的集成。 ![task-runner](./assets/imgs/topics/work-flow/task-runner.jpg) @@ -711,14 +716,12 @@ linter 是为了帮我我们找出 bug 而存在的,不要过分高估它的 Git,SVN 只是代码管理的工具,不等同于代码管理。 -项目代码需要有一个好的架构,需要高内聚低耦合,把各功能模块尽可能的分解成独立的, -在做真正的代码管路之前,我们要思考几个问题: +项目代码需要有一个好的架构,需要高内聚低耦合,把各功能模块尽可能的分解成独立的,在做真正的代码管路之前,我们要思考几个问题: - 我们为什么要做代码管理 ?不做代码管理可以么? - 做代码管理,主要的内容是什么? -最后我们再去研究怎么去做,这才是一个本应该有的流程。 -不要一上来就是分支管理,git 操作,git workflow 啥的,会被绕进去, +最后我们再去研究怎么去做,这才是一个本应该有的流程。不要一上来就是分支管理,git 操作,git workflow 啥的,会被绕进去, 到头来用工具解决了什么问题都不知道。 参考: @@ -756,14 +759,12 @@ Git,SVN 只是代码管理的工具,不等同于代码管理。 ### 跨端 -跨端开发是一种权衡,一种开发效率和极致性能的权衡。 -就好像虚拟 DOM 一样,虚拟 DOM 其实也是一种权衡,也是开发效率,维护性和极致性能之间的权衡。 +跨端开发是一种权衡,一种开发效率和极致性能的权衡。就好像虚拟 DOM 一样,虚拟 DOM 其实也是一种权衡,也是开发效率,维护性和极致性能之间的权衡。 如果你足够细心你会发现软件工程有很多这样的权衡。 #### flutter -Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 -Flutter 可以与现有的代码一起工作。在全世界,Flutter 正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。 +Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。Flutter 可以与现有的代码一起工作。在全世界,Flutter 正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。 #### RN @@ -828,7 +829,8 @@ Flutter 可以与现有的代码一起工作。在全世界,Flutter 正在被 闭着眼睛使用你的产品,测试看看它是否是"无障碍"的。在无法用眼看无法用鼠标,仅仅通过屏幕阅读软件对界面 的描述去操作你的产品时,人们还能顺利地使用那些呕心沥血做出来的功能吗? - [开发者必备 — Web 无障碍手册](https://link.zhihu.com/?target=https%3A//www.telerik.com/blogs/web-accessibility-guidebook-for-developers) - > 译文地址: https://zhuanlan.zhihu.com/p/76438798 + +> 译文地址: https://zhuanlan.zhihu.com/p/76438798 ### 新技术 🆕 @@ -951,8 +953,7 @@ Flutter 可以与现有的代码一起工作。在全世界,Flutter 正在被 本仓库的所有内容都是本人自己整理的,因此可能有不够完善,优秀甚至错误的地方,大家可以随意提问题。 -对于前端的技能图谱,我比较推荐 [这个网站](https://roadmap.sh/frontend)。 -这个网站相对于其他的前端技能图谱,更新地更快一点,其他的技能图谱很多都落伍了。 +对于前端的技能图谱,我比较推荐 [这个网站](https://roadmap.sh/frontend)。这个网站相对于其他的前端技能图谱,更新地更快一点,其他的技能图谱很多都落伍了。 ## 贡献 diff --git a/docs/topics/ts/leetcode-interview-ts.md b/docs/topics/ts/leetcode-interview-ts.md new file mode 100644 index 0000000..e714ecf --- /dev/null +++ b/docs/topics/ts/leetcode-interview-ts.md @@ -0,0 +1,341 @@ +--- +title: 想去力扣当前端,TypeScript 需要掌握到什么程度? +tags: [前端, TypeScript] +categories: + - [前端] + - [TypeScript] +--- + +2018 年底的时候,力扣发布了岗位招聘,其中就有前端,仓库地址:https://github.com/LeetCode-OpenSource/hire 。与大多数 JD 不同, 其提供了 5 道题, 并注明了`完成一个或多个面试题,获取免第一轮面试的面试机会。完成的题目越多,质量越高,在面试中的加分更多。完成后的代码可以任意形式发送给 jobs@lingkou.com。以上几个问题完成一个或多个都有可能获得面试机会,具体情况取决于提交给我们的代码。` + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1ggfv55mufyj30u00wh0z0.jpg) + +(力扣中国前端工程师 JD) + +今天我们就来看下第二题:`编写复杂的 TypeScript 类型`。通过这道题来看下, TypeScript 究竟要到什么水平才能进力扣当前端? + +> 其它四道题也蛮有意思的,值得一看。 + + + +## 问题描述 + +假设有一个叫 `EffectModule` 的类 + +```ts +class EffectModule {} +``` + +这个对象上的方法**只可能**有两种类型签名: + +```ts +interface Action { + payload?: T + type: string +} + +asyncMethod(input: Promise): Promise> + +syncMethod(action: Action): Action +``` + +这个对象上还可能有一些任意的**非函数属性**: + +```ts +interface Action { + payload?: T; + type: string; +} + +class EffectModule { + count = 1; + message = "hello!"; + + delay(input: Promise) { + return input.then((i) => ({ + payload: `hello ${i}!`, + type: "delay", + })); + } + + setMessage(action: Action) { + return { + payload: action.payload!.getMilliseconds(), + type: "set-message", + }; + } +} +``` + +现在有一个叫 `connect` 的函数,它接受 EffectModule 实例,将它变成另一个对象,这个对象上只有**EffectModule 的同名方法**,但是方法的类型签名被改变了: + +```ts +asyncMethod(input: Promise): Promise> 变成了 +asyncMethod(input: T): Action +``` + +```ts +syncMethod(action: Action): Action 变成了 +syncMethod(action: T): Action +``` + +例子: + +EffectModule 定义如下: + +```ts +interface Action { + payload?: T; + type: string; +} + +class EffectModule { + count = 1; + message = "hello!"; + + delay(input: Promise) { + return input.then((i) => ({ + payload: `hello ${i}!`, + type: "delay", + })); + } + + setMessage(action: Action) { + return { + payload: action.payload!.getMilliseconds(), + type: "set-message", + }; + } +} +``` + +connect 之后: + +```ts +type Connected = { + delay(input: number): Action; + setMessage(action: Date): Action; +}; +const effectModule = new EffectModule(); +const connected: Connected = connect(effectModule); +``` + +要求: + +在 [题目链接](https://codesandbox.io/s/4tmtp "题目链接") 里面的 `index.ts` 文件中,有一个 `type Connect = (module: EffectModule) => any`,将 `any` 替换成题目的解答,让编译能够顺利通过,并且 `index.ts` 中 `connected` 的类型与: + +```typescript +type Connected = { + delay(input: number): Action; + setMessage(action: Date): Action; +}; +``` + +**完全匹配**。 + +> 以上是官方题目描述,下面我的补充 + +上文提到的`index.ts` 比 题目描述多了两个语句,它们分别是: + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1ggfvu17znfj30u80eiad2.jpg) + +(题目额外信息) + +## 思路 + +首先来解读下题目。 题目要求我们补充类型 `Connect` 的定义, 也就是将 any 替换为不报错的其他代码。 + +回顾一下题目信息: + +- 有一个叫 `connect` 的函数,它接受 EffectModule 实例,将它变成另一个对象,这个对象上只有**EffectModule 的同名方法**,但是方法的类型签名被改变了 +- 这个对象上还可能有一些任意的**非函数属性** +- 这个对象(EffectModule 实例)上的方法**只可能**有两种类型签名 + +根据以上信息,我们能够得到:`我们只需要将作为参数传递进来的 EffectModule 实例上的函数类型签名修改一下,非函数属性去掉即可`。所以,我们有两件问题要解决: + +1. 如何将非函数属性去掉 +2. 如何转换函数类型签名 + +### 如何将非函数属性去掉 + +我们需要定义一个泛型,功能是接受一个对象,如果对象的 value 是 函数,则保留,否则去掉即可。不懂泛型的朋友可以先看下我之前写的文章: [你不知道的 TypeScript 泛型(万字长文,建议收藏)](https://lucifer.ren/blog/2020/06/16/ts-generics/ "你不知道的 TypeScript 泛型(万字长文,建议收藏)") + +这让我想起了官方提供的 Omit 泛型 `Omit`。举个例子: + +```ts +interface Todo { + title: string; + description: string; + completed: boolean; +} + +type TodoPreview = Omit; + +// description 属性没了 +const todo: TodoPreview = { + title: "Clean room", + completed: false, +}; +``` + +官方的 Omit 实现: + +```ts +type Pick = { + [P in K]: T[P]; +}; +type Exclude = T extends U ? never : T; +type Omit = Pick>; +``` + +实际上我们要做的就是 Omit 的变种,不是 Omit 某些 key,而是 Omit 值为非函数的 key。 + +由于 Omit 非函数实际就就是 Pick 函数,并且无需显式指定 key,因此我们的泛型只接受一个参数即可。 于是模仿官方的 `Pick` 写出了如下代码: + +```ts +// 获取值为函数的 key,形如: 'funcKeyA' | 'funcKeyB' +type PickFuncKeys = { + [K in keyof T]: T[K] extends Function ? K : never; +}[keyof T]; + +// 获取值为函数的 key value 对,形如: { 'funcKeyA': ..., 'funKeyB': ...} +type PickFunc = Pick>; +``` + +使用效果: + +```ts +interface Todo { + title: string; + description: string; + addTodo(): string; +} + +type AddTodo = PickFunc; + +const todo: AddTodo = { + addTodo() { + return "关注脑洞前端~"; + }, +}; + +type ADDTodoKey = PickFuncKeys; // 'addTodo' +``` + +可以看出,PickFunc 只提取了函数属性,忽略了非函数属性。 + +### 如何转换函数类型签名 + +我们再来回顾一下题目要求: + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1ggfy6bz4prj31mo0b8goj.jpg) + +也就是我们需要知道**怎么才能提取 Promise 和 Action 泛型中的值**。 + +实际上这两个几乎一样,会了一个,另外一个也就会了。我们先来看下 `Promise`。 + +从: + +```ts +(arg: Promise) => Promise +``` + +变为: + +```ts +(arg: T) => U; +``` + +如果想要完成这个需求,需要借助`infer`。只需要在类型前加一个关键字前缀 `infer`,TS 会将推导出的类型自动填充进去。 + +infer 最早出现在此 [官方 PR](https://github.com/Microsoft/TypeScript/pull/21496) 中,表示在 extends 条件语句中待推断的类型变量。 + +简单示例如下: + +```ts +type ParamType = T extends (param: infer P) => any ? P : T; +``` + +在这个条件语句 `T extends (param: infer P) => any ? P : T` 中,infer P 表示待推断的函数参数。 + +整句表示为:如果 T 能赋值给 (param: infer P) => any,则结果是 (param: infer P) => any 类型中的参数 P,否则返回为 T。 + +一个更具体的例子: + +```ts +interface User { + name: string; + age: number; +} + +type Func = (user: User) => void; + +type Param = ParamType; // Param = User +type AA = ParamType; // string +``` + +这些知识已经够我们用了。 更多用法可以参考 [深入理解 TypeScript - infer](https://jkchao.github.io/typescript-book-chinese/tips/infer.html#%E4%BB%8B%E7%BB%8D "深入理解 TypeScript - infer") 。 + +根据上面的知识,不难写出如下代码: + +```ts +type ExtractPromise

= { + [K in PickFuncKeys

]: P[K] extends ( + arg: Promise + ) => Promise + ? (arg: T) => U + : never; +}; +``` + +提取 Action 的 代码也是类似: + +```ts +type ExtractAction

= { + [K in keyof PickFunc

]: P[K] extends ( + arg: Action + ) => Action + ? (arg: T) => Action + : never; +}; +``` + +至此我们已经解决了全部两个问题,完整代码见下方代码区。 + +## 关键点 + +- 泛型 +- extends 做类型约束 +- infer 做类型提取 +- 内置基本范型的使用和实现 + +## 代码 + +我们将这几个点串起来,不难写出如下最终代码: + +```ts +type ExtractContainer

= { + [K in PickFuncKeys

]: + P[K] extends (arg: Promise) => Promise ? (arg: T) => U : + P[K] extends (arg: Action) => Action ? (arg: T) => Action : + never +type Connect = (module: EffectModule) => ExtractContainer +``` + +完整代码在我的 [Gist](https://gist.github.com/azl397985856/5aecb2e221dc1b9b15af34680acb6ccf "Gist 地址") 上。 + +## 总结 + +我们先对问题进行定义,然后分解问题为:`1. 如何将非函数属性去掉`, `2. 如何转换函数类型签名`。最后从分解的问题,以及基础泛型工具入手,联系到可能用到的语法。 + +这个题目不算难,最多只是中等。但是你可能也看出来了,其不仅仅是考一个语法和 API 而已,而是考综合实力。这点在其他四道题体现地尤为明显。这种考察方式能真正考察一个人的综合实力,背题是背不来的。我个人在面试别人的时候也非常喜欢问这种问题。 + +只有**掌握基础 + 解决问题的思维方法**,面对复杂问题才能从容不迫,手到擒来。 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) + +知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70 " Lucifer - 知乎")】 + +点关注,不迷路! diff --git a/docs/topics/ts/ts-config.md b/docs/topics/ts/ts-config.md new file mode 100644 index 0000000..b749381 --- /dev/null +++ b/docs/topics/ts/ts-config.md @@ -0,0 +1,362 @@ +--- +title: TypeScript 配置文件该怎么写? +tags: [前端, TypeScript] +date: 2020-08-24 +categories: + - [前端, TypeScript] +--- + +TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: + +- 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在**逻辑上**比较零散。 +- 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 +- 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 + +因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 + +系列安排: + +- [上帝视角看 TypeScript(已发布)](https://lucifer.ren/blog/2020/08/04/ts-internal/) +- [TypeScript 类型系统(已发布)](https://lucifer.ren/blog/2020/08/15/ts-type-system/) +- [types 和 @types 是什么?(已发布)](https://lucifer.ren/blog/2020/08/21/ts-type/) +- [你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布)](https://lucifer.ren/blog/2020/06/16/ts-generics/) +- TypeScript 配置文件该怎么写?(就是本文) +- TypeScript 是如何与 React,Vue,Webpack 集成的? +- TypeScript 练习题 + +> 目录将来可能会有所调整。 + +注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 + +- [深入理解 TypeScript](https://jkchao.github.io/typescript-book-chinese/) +- [官方文档](https://www.typescriptlang.org/docs/home) + +结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 + +接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 + + + +## 前言 + +这篇文章是我的 TypeScript 系列的**第 5 篇**。今天我们就来看下, TypeScript 的配置文件 tsconfig.json 该如何写。 + +和 package.json 一样, 它也是一个 JSON 文件。package.json 是包描述文件,对应的 Commonjs 规范,而 **tsconfig.json 是最终被 TypeScript Compiler 解析和使用的一个 JSON 文件**。 TypeScript Compiler 用这个配置文件来决定如何对项目进行编译。 + +说到编译,不得不提一个知名选手 - [babel](https://www.babeljs.cn/docs/babel-cli)。 和 TypeScript 类似, 他们都可以将一种语法静态编译成另外一种语法。如果说我想编译一个文件,我只需要告诉 babel 我的文件路径即可。 + +```bash +npx babel script.js +``` + +有时候我想编译整个文件夹: + +```bash +npx babel src --out-dir lib +``` + +babel 也可以指定输出目录,指定需要忽略的文件或目录等等, TypeScript 也是一样!你当然可以像 babel 一样在命令行中全部指定好,也可以将这些配置放到 tsconfig.json 中,以配置文件的形式传递给 TypeScript Compiler 。 这就是 tsconfig.json 文件的初衷,即接受用户输入作为配置项。 + +## 初探 tsconfig + +我们先来看一个简单的 tsconfig 文件。 + +```json +{ + "compilerOptions": { + "outDir": "./built", + "allowJs": true, + "target": "es5" + }, + "include": ["./src/**/*"] +} +``` + +如上配置做了: + +- 读取所有可识别的 src 目录下的文件(通过 include)。 +- 接受 JavaScript 做为输入(通过 allowJs)。 +- 生成的所有文件放在 built 目录下(通过 outDir)。 +- 将 JavaScript 代码降级到低版本比如 ECMAScript 5(通过 target)。 + +实际项目有比这个更复杂。 接下来, 我们来进一步解读。 不过在讲配置项之前,我们先来看下 tsconfig.json 是如何被解析的。 + +## tsconfig 是如何被解析的? + +**如果一个目录下存在一个 tsconfig.json 文件,那么意味着这个目录是 TypeScript 项目的根目录。** 如果你使用 tsc 编译你的项目,并且没有显式地指定配置文件的路径,那么 tsc 则会逐级向上搜索父目录寻找 tsconfig.json ,这个过程类似 node 的模块查找机制。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gi0ppn43slj31760fe0vo.jpg) + +如图: + +- 在 \_uglify-js@3.7.2@uglify-js 下执行 tsc 则会找到 配置文件 1,在 \_uglify-js@3.7.2@uglify-js/bin 下执行 tsc 也会找到 配置文件 1 +- 同理在 lib,node_modules 也会找到 配置文件 1 +- 在 \_uglify-js@3.7.2@uglify-js/bin/lucifer 下执行 tsc 则会找到 配置文件 2 +- 在 \_uglify-js@3.7.2@uglify-js/lib/lucifer 下执行 tsc 则会找到 配置文件 3 + +我在 [上帝视角看 TypeScript](https://lucifer.ren/blog/2020/08/04/ts-internal/) 一种讲述了 TypeScript 究竟做了什么,带你从宏观的角度看了一下 TypeScript。 其中提到了 TypeScript 编译器会接受文件或者文件集合作为输入,最终转换为 JavaScript(noEmit 为 false) 和 .d.ts(declarations 为 true)。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gh5ld0kfitj30ow0csdjs.jpg) + +这里其实还少了一个点,那就是除了接受文件或者文件集合作为输入,还会接受 tsconfig.json。tsconfig.json 的内容决定了编译的范围和行为,不同的 配置可能会得到不同的输出,或者得到不同的检查结果。 + +当 tsc 找到了一个 tsconfig.json 文件,那么其规定的编译目录则全部会被 typescript 处理,当然也包括其依赖的文件。 如果 tsc 没有找到一个 tsconfig.json 或 tsconfig 没有有效信息,那么 tsc 会使用默认配置。 比如 tsconfig 是一个空的就没有有效信息: + +```json +{} +``` + +> tsconfig 的全部属性,以及属性的默认值可以在这里找到: http://json.schemastore.org/tsconfig + +总结一下 tsc 解析 tsconfig.json 的逻辑。 + +- 如果命令行指定了配置选项或者指定了配置文件的路径,那么直接会读取。 + - 根据 [tsconfig json schema](http://json.schemastore.org/tsconfig) 校验是否格式正确。 + - 如果正确,则将其和默认配置合并(如果有 extends 字段,也会一起合并),将合并后的配置传递给 TypeScript 编译器并开始编译。 + - 否则抛出错误 +- 否则,会从当前目录查找 tsconfig.json 文件, 如果找不到则逐层向上搜索父目录。 + - 如果找到了则会去根据 [tsconfig json schema](http://json.schemastore.org/tsconfig) 校验是否格式正确。 + - 如果正确,则将其和默认配置合并(如果有 extends 字段,也会一起合并),将合并后的配置传递给 TypeScript 编译器并开始编译。 + - 否则抛出错误 + - 否则,始终找不到则直接使用默认配置 + +## tsconfig 的顶层属性 + +tsconfig 的顶层属性(Top Level)不多,主要有:**compilerOptions, files, include, exclude,extends,compileOnSave**等。 + +- compilerOptions 是重头戏,其属性也是最多的,我们的项目也是对这个定制比较多,这个我后面会重点讲。 +- files 则是你需要编译的文件 +- exclude 则是你不需要编译的文件目录(支持 glob) +- include 是你需要编译的文件目录(支持 glob) +- extends 就是继承另外一个配置文件,TypeScript 会对其进行合并,多项目公共配置有用。你也可以直接继承社区的“最佳实践”,比如: + +```json +{ + "extends": "@tsconfig/node12/tsconfig.json", + + "compilerOptions": {}, + + "include": ["src/**/*"], + "exclude": ["node_modules"] +} +``` + +- compileOnSave 则是和编辑器(确切地说是文件系统)联动的配置,即是否在文件保存后进行编译,实际项目不建议使用。 + +除了 compilerOptions,其他也相对比较好理解。 因此接下来我只针对 compilerOptions 详细讲解一番。 + +## tsconfig 的编译项 + +详细全面的内容,大家只需要参考[官网](https://www.typescriptlang.org/tsconfig "官网-tsconfig")的就好了。官网写的不仅全面,而且做了分类,非常清晰。 + +接下来,我会根据功能分开讲几个**常用** 的配置。 + +### 文件相关 + +常用的是以下四个,由于前面已经做了介绍,因此就不赘述了。 + +- exclude +- extends +- files +- include + +### 严格检查 + +- alwaysStrict + +默认:false + +首次发布版本:2.1 + +这个是和 ECMAScript 规范相关的,工作机制和 ES 5 的严格模式一样, 并且输出的 JS 顶部也会也会带上 'use strict'。 + +- noImplicitAny(推荐打开) + +默认:true + +首次发布版本:- + +我在 - [TypeScript 类型系统](https://lucifer.ren/blog/2020/08/15/ts-type-system/) 中提到了如果不对变量显式声明类型,那么 TypeScript 会对变量进行类型推导,这当然也有推导不出的情况,这个时候该变量的类型就是 any,这个叫做隐式 any。区别于显式 any: + +```ts +const a: any = {}; +``` + +隐式 any 是 TypeScript 编译器推断的。 + +- noImplicitThis(推荐打开) + +默认:true + +首次发布版本:2.0 + +和隐式 any 类型, 只不过这次是针对的特殊的一个关键字 this,也就是你需要显式地指定 this 的类型。 + +- strict(推荐打开) + +默认:true + +首次发布版本:2.3 + +实际上 strict 只是一个简写,是多个规则的合集。 类似于 babel 中插件(plugins)和 预设(presets)的差别。换句话说如果你指定了 strict 为 true ,那么所有严格相关的规则的都会开启,我所讲的**严格检查**都是,还有一部分我没有提到的。另外将来如果增加更多严格规则,你只要开启了 strict 则会自动加进来。 + +### 模块解析 + +#### 模块相关 + +目的:**allowSyntheticDefaultImports,allowUmdGlobalAccess,esModuleInterop,moduleResolution 都是为了和其他模块化规范兼容做的。** + +- allowSyntheticDefaultImports +- allowUmdGlobalAccess +- esModuleInterop +- moduleResolution + +还有一个配置 **module**,规定了项目的模块化方式,选项有 AMD,UMD,commonjs 等。 + +#### 路径相关 + +目的: **baseUrl,paths,rootDirs, typeRoots,types 都是为了简化路径的拼写做的。** + +- baseUrl + +这个配置是告诉 TypeScript 如何解析模块路径的。比如: + +```ts +import { helloWorld } from "hello/world"; + +console.log(helloWorld); +``` + +这个就会从 baseUrl 下找 hello 目录下的 world 文件。 + +- paths + +定义类似别名的存在,从而简化路径的书写。 + +- rootDirs + +注意是 rootDirs ,而不是 rootDir,也就是说根目录可以有多个。 当你指定了多个根目录的时候, 不同根目录的文件可以像在一个目录下一样互相访问。 + +> 实际上也有一个叫 rootDir 的, 和 rootDirs 的区别就是其只能指定一个。 + +- typeRoots +- types + +types 和 typeRoots 我在 - [types 和 @types 是什么?](https://lucifer.ren/blog/2020/08/21/ts-type/) 已经讲得很清楚了,这里就不多说了。 + +### 项目配置 + +#### JavaScript 相关 + +- allowJs + +默认:false + +首次发布版本:1.8 + +顾名思义,允许在 TypeScript 项目中使用 JavaScript,这在从 JavaScript 迁移到 TypeScript 中是非常重要的。 + +- checkJs + +默认:false + +首次发布版本:- + +和 allowJs 类似, 只不过 checkJs 会额外对 JS 文件进行校验。 + +#### 声明文件相关 + +如果 TypeScript 是将 TS 文件编译为 JS,那么声明文件 + JS 文件就可以反推出 TS 文件。 + +这两个用来生成 .d.ts 和 .d.ts 的 sourcemap 文件。 + +- declaration + +默认:false + +首次发布版本:1.0 + +- declarationMap + +默认:false + +首次发布版本:2.9 + +#### 外部库相关 + +- jsx + +默认:react + +首次发布版本:2.2 + +这个是告诉 TypeScript 如何编译 jsx 语法的。 + +- lib + +默认:- + +首次发布版本:2.0 + +lib 我在 [TypeScript 类型系统](https://lucifer.ren/blog/2020/08/15/ts-type-system/) 中讲过。 Typescript 提供了诸如 lib.d.ts 等类型库文件。随着 ES 的不断更新, JavaScript 类型和全局变量会逐渐变多。Typescript 也是采用这种 lib 的方式来解决的。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghmyjgyd7qj307o0lxgmq.jpg) + +(TypeScript 提供的部分 lib) + +#### 输出相关 + +outDir 和 outFile 这两个配置则是告诉 TypeScript 将文件生成到哪里。 + +- outDir + +默认:和 ts 文件同目录(且同名,只是后缀不同) + +首次发布版本:- + +- outFile + +默认:- + +首次发布版本:1.0 + +module 是 CommonJS 和 ES6 module 不能知道 outFile,只有是 None, System 或 AMD 才行,其会将这些模块的文件内容打包到全局文件内容之后。 + +而 noEmit 则是控制是否输出 JS 文件的。 + +- noEmit + +默认:false + +首次发布版本:- + +如果你只希望用 TypeScript 进行类型检查,不希望要它生成文件,则可以将 noEmit 设置成 true。 + +- target + +即输出的 JavaScript 对标的 ECMA 规范。 比如 “target”: “es6” 就是将 es6 + 的语法转换为 ES6 的 代码。其选项有 ES3,ES5,ES6 等。 + +> 为什么没有 ES4 ? ^\_^ + +## 总结 + +- tsconfig 就是一个 JSON 文件,TypeScript 会使用该文件来决定如何编译和检查 TypeScript 项目。和 babel 类似,甚至很多配置项都是相通的。 + +- 如果一个目录下存在一个 tsconfig.json 文件,那么意味着这个目录是 TypeScript 项目的根目录。 如果你使用 tsc 编译你的项目,并且没有显式地指定配置文件的路径,那么 tsc 则会逐级向上搜索父目录寻找 tsconfig.json ,这个过程类似 node 的模块查找机制。 + +- tsconfig 中最重要的恐怕就是编译器选项(compilerOptions)了。如果你按照功能去记忆则会比较简单, 比如文件相关的有哪些, 严格检查的有哪些,声明文件的有哪些等等。 + +## 参考 + +- [typescriptlang's tsconfig](https://www.typescriptlang.org/tsconfig#jsx) + +## 关注我 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) + +公众号【 [力扣加加](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)】 +知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 + +点关注,不迷路! diff --git a/docs/topics/ts/ts-exercises-2.md b/docs/topics/ts/ts-exercises-2.md new file mode 100644 index 0000000..2125572 --- /dev/null +++ b/docs/topics/ts/ts-exercises-2.md @@ -0,0 +1,1498 @@ +--- +title: TypeScript 练习题(第二弹) +tags: [前端, TypeScript] +date: 2020-10-13 +categories: + - [前端, TypeScript] +--- + +TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: + +- 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在**逻辑上**比较零散。 +- 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 +- 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 + +因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 + +系列安排: + +- [上帝视角看 TypeScript](https://lucifer.ren/blog/2020/08/04/ts-internal/) +- [TypeScript 类型系统](https://lucifer.ren/blog/2020/08/15/ts-type-system/) +- [types 和 @types 是什么?](https://lucifer.ren/blog/2020/08/21/ts-type/) +- [你不知道的 TypeScript 泛型(万字长文,建议收藏)](https://lucifer.ren/blog/2020/06/16/ts-generics/) +- [TypeScript 配置文件该怎么写?](https://lucifer.ren/blog/2020/08/24/ts-config/) +- TypeScript 是如何与 React,Vue,Webpack 集成的? +- [TypeScript 练习题(第一弹)](https://lucifer.ren/blog/2020/09/27/ts-exercises/) + +> 目录将来可能会有所调整。 + +注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 + +- [深入理解 TypeScript](https://jkchao.github.io/typescript-book-chinese/ "深入理解 TypeScript") +- [TypeScript 官方文档](https://www.typescriptlang.org/docs/home "TypeScript 官方文档") + +结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 + +接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 + + + +## 前言 + +本文涉及的题目一共十六道,全部都可以在 [typescript-exercises](https://typescript-exercises.github.io/ "typescript-exercises") 上在线提交。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj1sl6j7pij31560p4ad7.jpg) + +可以和标准答案进行对比。 +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj1slk6r7bj31g30o9djp.jpg) + +并且由于使用了浏览器缓存, 因此无需登录的情况下也可以保证关掉页面,你的答题进度也会保留。 + +> 想重置进度,清空缓存,无痕模式或者换浏览器都可以。 + +题目中涉及到的知识点我基本也都在之前的文章中提到了,如果你没有看过,强烈建议先完成前面的教程,然后将上面的题目自己做一遍之后再看本文。另外一定要按照顺序读, 因此前面的题目都是后面的铺垫。 + +为了不让文章太过于冗长, 本篇文章分两次发布, 一次 8 道题,一共十五道。每道题都有思路,前置知识以及代码。 **这次给大家带来的是后 6 道** + +> 其中有一道题需要大家有函数式编程的知识, 如果大家不知道会比较难以解释。 为了避免内容太过分散,将这道题从我的题解中移除,故只有 6 道。 + +## 题目九 + +### 题目描述 + +``` +Intro: + + PowerUsers idea was bad. Once those users got + extended permissions, they started bullying others + and we lost a lot of great users. + As a response we spent all the remaining money + on the marketing and got even more users. + We need to start preparing to move everything to a + real database. For now we just do some mocks. + + The server API format was decided to be the following: + + In case of success: { status: 'success', data: RESPONSE_DATA } + In case of error: { status: 'error', error: ERROR_MESSAGE } + + The API engineer started creating types for this API and + quickly figured out that the amount of types needed to be + created is too big. + +Exercise: + + Remove UsersApiResponse and AdminsApiResponse types + and use generic type ApiResponse in order to specify API + response formats for each of the functions. + +``` + +题目的大概意思是:之前都是写死的数据, 现在数据需要从接口拿,请你定义这个接口的类型。 + +### 题目内置代码 + +```ts +interface User { + type: "user"; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: "admin"; + name: string; + age: number; + role: string; +} + +type Person = User | Admin; + +const admins: Admin[] = [ + { type: "admin", name: "Jane Doe", age: 32, role: "Administrator" }, + { type: "admin", name: "Bruce Willis", age: 64, role: "World saver" }, +]; + +const users: User[] = [ + { + type: "user", + name: "Max Mustermann", + age: 25, + occupation: "Chimney sweep", + }, + { type: "user", name: "Kate Müller", age: 23, occupation: "Astronaut" }, +]; + +export type ApiResponse = unknown; + +type AdminsApiResponse = + | { + status: "success"; + data: Admin[]; + } + | { + status: "error"; + error: string; + }; + +export function requestAdmins(callback: (response: AdminsApiResponse) => void) { + callback({ + status: "success", + data: admins, + }); +} + +type UsersApiResponse = + | { + status: "success"; + data: User[]; + } + | { + status: "error"; + error: string; + }; + +export function requestUsers(callback: (response: UsersApiResponse) => void) { + callback({ + status: "success", + data: users, + }); +} + +export function requestCurrentServerTime( + callback: (response: unknown) => void +) { + callback({ + status: "success", + data: Date.now(), + }); +} + +export function requestCoffeeMachineQueueLength( + callback: (response: unknown) => void +) { + callback({ + status: "error", + error: "Numeric value has exceeded Number.MAX_SAFE_INTEGER.", + }); +} + +function logPerson(person: Person) { + console.log( + ` - ${person.name}, ${person.age}, ${ + person.type === "admin" ? person.role : person.occupation + }` + ); +} + +function startTheApp(callback: (error: Error | null) => void) { + requestAdmins((adminsResponse) => { + console.log("Admins:"); + if (adminsResponse.status === "success") { + adminsResponse.data.forEach(logPerson); + } else { + return callback(new Error(adminsResponse.error)); + } + + console.log(); + + requestUsers((usersResponse) => { + console.log("Users:"); + if (usersResponse.status === "success") { + usersResponse.data.forEach(logPerson); + } else { + return callback(new Error(usersResponse.error)); + } + + console.log(); + + requestCurrentServerTime((serverTimeResponse) => { + console.log("Server time:"); + if (serverTimeResponse.status === "success") { + console.log( + ` ${new Date(serverTimeResponse.data).toLocaleString()}` + ); + } else { + return callback(new Error(serverTimeResponse.error)); + } + + console.log(); + + requestCoffeeMachineQueueLength((coffeeMachineQueueLengthResponse) => { + console.log("Coffee machine queue length:"); + if (coffeeMachineQueueLengthResponse.status === "success") { + console.log(` ${coffeeMachineQueueLengthResponse.data}`); + } else { + return callback(new Error(coffeeMachineQueueLengthResponse.error)); + } + + callback(null); + }); + }); + }); + }); +} + +startTheApp((e: Error | null) => { + console.log(); + if (e) { + console.log( + `Error: "${e.message}", but it's fine, sometimes errors are inevitable.` + ); + } else { + console.log("Success!"); + } +}); +``` + +### 前置知识 + +- 泛型 +- 回调函数 + +### 思路 + +我们还是直接看报错。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gjnvjdkj9qj30d70350sy.jpg) + +很明显这个报错的原因是类型是 unknown, 因此我们只有将 unknown 改成正确的类型即可。 + +换句话说, 就是把这种地方改成正确类型即可。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gjnvkm31ecj30j103o74j.jpg) + +题目描述说了, 这个 response 其实是从后端返回的。 而后端返回的数据有固定的格式。比如获取用户列表接口: + +```ts +type UsersApiResponse = + | { + status: "success"; + data: User[]; + } + | { + status: "error"; + error: string; + }; +``` + +其他接口也是类似, 不同的是 data 的类型。因此我们考虑使用泛型封装,将 data 的类型作为参数即可。 + +从本质上来说, 就是从后端取的数据有两种大的可能, 一种是错误, 一种是成功。两者在同一接口同一时刻只会出现一个,且必须出现一个。 + +而成功的情况又会随着接口不同从而可能产生不同的类型。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gjoppxyseyj30js0f4t9m.jpg) + +这是明显的使用 **或逻辑关系** 和**泛型进行类型定义**的强烈信号。 +我们可以使用泛型做如下改造: + +```ts +export type ApiResponse = + | { + status: "success"; + data: T; + } + | { + status: "error"; + error: string; + }; +``` + +那么上面的 UsersApiResponse 就可以变成: + +```ts +type UsersApiResponse = ApiResponse; +``` + +不懂的同学建议看下我之前的文章:- [你不知道的 TypeScript 泛型(万字长文,建议收藏)](https://lucifer.ren/blog/2020/06/16/ts-generics/) + +用同样的套路把其他后端返回加上类型即可。 + +### 代码 + +核心代码: + +```ts +export type ApiResponse = + | { + status: "success"; + data: T; + } + | { + status: "error"; + error: string; + }; + +export function requestAdmins( + callback: (response: ApiResponse) => void +) { + callback({ + status: "success", + data: admins, + }); +} + +export function requestUsers( + callback: (response: ApiResponse) => void +) { + callback({ + status: "success", + data: users, + }); +} + +export function requestCurrentServerTime( + callback: (response: ApiResponse) => void +) { + callback({ + status: "success", + data: Date.now(), + }); +} + +export function requestCoffeeMachineQueueLength( + callback: (response: ApiResponse) => void +) { + callback({ + status: "error", + error: "Numeric value has exceeded Number.MAX_SAFE_INTEGER.", + }); +} +``` + +## 题目十 + +### 题目描述 + +``` +Intro: + + We have asynchronous functions now, advanced technology. + This makes us a tech startup officially now. + But one of the consultants spoiled our dreams about + inevitable future IT leadership. + He said that callback-based asynchronicity is not + popular anymore and everyone should use Promises. + He promised that if we switch to Promises, this would + bring promising results. + +Exercise: + + We don't want to reimplement all the data-requesting + functions. Let's decorate the old callback-based + functions with the new Promise-compatible result. + The final function should return a Promise which + would resolve with the final data directly + (i.e. users or admins) or would reject with an error + (or type Error). + + The function should be named promisify. + +Higher difficulty bonus exercise: + + Create a function promisifyAll which accepts an object + with functions and returns a new object where each of + the function is promisified. + + Rewrite api creation accordingly: + + const api = promisifyAll(oldApi); +``` + +题目大意是:前面用的是基于 callback 形式的代码, 他们对代码进行了重构,改造成了 Promise,让你对基于 Promise 的接口进行类型定义。 + +### 题目内置代码 + +```ts +interface User { + type: "user"; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: "admin"; + name: string; + age: number; + role: string; +} + +type Person = User | Admin; + +const admins: Admin[] = [ + { type: "admin", name: "Jane Doe", age: 32, role: "Administrator" }, + { type: "admin", name: "Bruce Willis", age: 64, role: "World saver" }, +]; + +const users: User[] = [ + { + type: "user", + name: "Max Mustermann", + age: 25, + occupation: "Chimney sweep", + }, + { type: "user", name: "Kate Müller", age: 23, occupation: "Astronaut" }, +]; + +export type ApiResponse = + | { + status: "success"; + data: T; + } + | { + status: "error"; + error: string; + }; + +export function promisify(arg: unknown): unknown { + return null; +} + +const oldApi = { + requestAdmins(callback: (response: ApiResponse) => void) { + callback({ + status: "success", + data: admins, + }); + }, + requestUsers(callback: (response: ApiResponse) => void) { + callback({ + status: "success", + data: users, + }); + }, + requestCurrentServerTime(callback: (response: ApiResponse) => void) { + callback({ + status: "success", + data: Date.now(), + }); + }, + requestCoffeeMachineQueueLength( + callback: (response: ApiResponse) => void + ) { + callback({ + status: "error", + error: "Numeric value has exceeded Number.MAX_SAFE_INTEGER.", + }); + }, +}; + +export const api = { + requestAdmins: promisify(oldApi.requestAdmins), + requestUsers: promisify(oldApi.requestUsers), + requestCurrentServerTime: promisify(oldApi.requestCurrentServerTime), + requestCoffeeMachineQueueLength: promisify( + oldApi.requestCoffeeMachineQueueLength + ), +}; + +function logPerson(person: Person) { + console.log( + ` - ${person.name}, ${person.age}, ${ + person.type === "admin" ? person.role : person.occupation + }` + ); +} + +async function startTheApp() { + console.log("Admins:"); + (await api.requestAdmins()).forEach(logPerson); + console.log(); + + console.log("Users:"); + (await api.requestUsers()).forEach(logPerson); + console.log(); + + console.log("Server time:"); + console.log( + ` ${new Date(await api.requestCurrentServerTime()).toLocaleString()}` + ); + console.log(); + + console.log("Coffee machine queue length:"); + console.log(` ${await api.requestCoffeeMachineQueueLength()}`); +} + +startTheApp().then( + () => { + console.log("Success!"); + }, + (e: Error) => { + console.log( + `Error: "${e.message}", but it's fine, sometimes errors are inevitable.` + ); + } +); +``` + +### 前置知识 + +- Promise +- promisify +- 泛型 +- 高阶函数 + +### 思路 + +题目给了一个 promisefy, 并且类型都是 unknown,不难看出, 它就是想让我们改造 promisefy 使其不报错, 并能正确推导类型。 + +```ts +export function promisify(arg: unknown): unknown { + return null; +} +``` + +我们先不考虑这个类型怎么写,先把 promiify 实现一下再说。这需要你有一点高阶函数和 promise 的知识。由于这不是本文的重点,因此不赘述。 + +```ts +export function promisify(fn) { + return () => + new Promise((resolve, reject) => { + fn((response) => { + if (response.status === "success") resolve(response.data); + else reject(response.error); + }); + }); +} +``` + +接下来,我们需要给其增加类型签名。 + +这个 fn 实际上是一个函数,并且又接受一个 callback 作为参数。 因此大概是这个样子: + +```ts +((something) = void) => void +``` + +这里的 something 实际上我们在上一节已经解决了,直接套用即可。代码: + +```ts +(callback: (response: ApiResponse) => void) => void +``` + +整体代码大概是: + +```ts +export function promisify( + fn: (callback: (response: ApiResponse) => void) => void +): () => Promise { + // 上面的实现 +} +``` + +### 代码 + +核心代码: + +```ts +export function promisify( + fn: (callback: (response: ApiResponse) => void) => void +): () => Promise { + return () => + new Promise((resolve, reject) => { + fn((response) => { + if (response.status === "success") resolve(response.data); + else reject(response.error); + }); + }); +} +``` + +## 第十一题 + +### 题目描述 + +``` +Intro: + + In order to engage users in the communication with + each other we have decided to decorate usernames + in various ways. A brief search led us to a library + called "str-utils". Bad thing is that it lacks + TypeScript declarations. + +Exercise: + + Check str-utils module implementation at: + node_modules/str-utils/index.js + node_modules/str-utils/README.md + + Provide type declaration for that module in: + declarations/str-utils/index.d.ts + + Try to avoid duplicates of type declarations, + use type aliases. +``` + +题目的意思是他们用到了一个库 `str-utils`,这个库的人又没给我们写类型定义,于是我们不得不去自己写(好真实的例子啊)。 + +其实就是让我们实现以下函数的类型签名: + +```ts +import { + strReverse, + strToLower, + strToUpper, + strRandomize, + strInvertCase, +} from "str-utils"; +``` + +### 题目内置代码 + +```ts +// declarations/str-utils/index.d.js +declare module "str-utils" { + // export const ... + // export function ... +} + +// index.ts +import { + strReverse, + strToLower, + strToUpper, + strRandomize, + strInvertCase, +} from "str-utils"; + +interface User { + type: "user"; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: "admin"; + name: string; + age: number; + role: string; +} + +type Person = User | Admin; + +const admins: Admin[] = [ + { type: "admin", name: "Jane Doe", age: 32, role: "Administrator" }, + { type: "admin", name: "Bruce Willis", age: 64, role: "World saver" }, + { type: "admin", name: "Steve", age: 40, role: "Steve" }, + { type: "admin", name: "Will Bruces", age: 30, role: "Overseer" }, + { type: "admin", name: "Superwoman", age: 28, role: "Customer support" }, +]; + +const users: User[] = [ + { + type: "user", + name: "Max Mustermann", + age: 25, + occupation: "Chimney sweep", + }, + { type: "user", name: "Kate Müller", age: 23, occupation: "Astronaut" }, + { type: "user", name: "Moses", age: 70, occupation: "Desert guide" }, + { type: "user", name: "Superman", age: 28, occupation: "Ordinary person" }, + { type: "user", name: "Inspector Gadget", age: 31, occupation: "Undercover" }, +]; + +const isAdmin = (person: Person): person is Admin => person.type === "admin"; +const isUser = (person: Person): person is User => person.type === "user"; + +export const nameDecorators = [ + strReverse, + strToLower, + strToUpper, + strRandomize, + strInvertCase, +]; + +function logPerson(person: Person) { + let additionalInformation: string = ""; + if (isAdmin(person)) { + additionalInformation = person.role; + } + if (isUser(person)) { + additionalInformation = person.occupation; + } + const randomNameDecorator = + nameDecorators[Math.round(Math.random() * (nameDecorators.length - 1))]; + const name = randomNameDecorator(person.name); + console.log(` - ${name}, ${person.age}, ${additionalInformation}`); +} + +([] as Person[]).concat(users, admins).forEach(logPerson); + +// In case if you are stuck: +// https://www.typescriptlang.org/docs/handbook/modules.html#ambient-modules +``` + +### 前置知识 + +- 如何给缺乏类型定义的第三方库定义类型 + +### 思路 + +这个题目的考点就是**如何给缺乏类型定义的第三方库定义类型**。 + +这个时候我们只要新建一个文件然后加入以下代码即可。 + +```ts +declare module "str-utils" { + // 在这里定义类型 + // export const ... + // export function ... +} +``` + +其中 str-utils 是那个可恶的没有类型定义的库的名字。 + +有了这个知识,我们的代码就简单了。 + +### 代码 + +```ts +declare module "str-utils" { + // export const ... + // export function ... + export function strReverse(s: string): string; + export function strToLower(s: string): string; + export function strToUpper(s: string): string; + export function strRandomize(s: string): string; + export function strInvertCase(s: string): string; +} +``` + +## 第十二题 + +### 题目描述 + +``` +Intro: + + We have so many users and admins in the database! + CEO's father Jeff says that we are a BigData + startup now. We have no idea what it means, but + Jeff says that we need to do some statistics and + analytics. + + We've ran a questionnaire within the team to + figure out what do we know about statistics. + The only person who filled it was our coffee + machine maintainer. The answers were: + + * Maximums + * Minumums + * Medians + * Averages + + We found a piece of code on stackoverflow and + compiled it into a module `stats`. The bad + thing is that it lacks type declarations. + +Exercise: + + Check stats module implementation at: + node_modules/stats/index.js + node_modules/stats/README.md + + Provide type declaration for that module in: + declarations/stats/index.d.ts + +Higher difficulty bonus exercise: + + Avoid duplicates of type declarations. +``` + +题目大概意思是又来了一个库,这个库又没有写定义,我们又要自己写。 (真实++) + +### 题目内置代码 + +```ts +// declartions/stats/index.d.ts +declare module "stats" { + export function getMaxIndex(input: unknown, comparator: unknown): unknown; +} + +// index.ts +import { + getMaxIndex, + getMaxElement, + getMinIndex, + getMinElement, + getMedianIndex, + getMedianElement, + getAverageValue, +} from "stats"; + +interface User { + type: "user"; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: "admin"; + name: string; + age: number; + role: string; +} + +const admins: Admin[] = [ + { type: "admin", name: "Jane Doe", age: 32, role: "Administrator" }, + { type: "admin", name: "Bruce Willis", age: 64, role: "World saver" }, + { type: "admin", name: "Steve", age: 40, role: "Steve" }, + { type: "admin", name: "Will Bruces", age: 30, role: "Overseer" }, + { type: "admin", name: "Superwoman", age: 28, role: "Customer support" }, +]; + +const users: User[] = [ + { + type: "user", + name: "Max Mustermann", + age: 25, + occupation: "Chimney sweep", + }, + { type: "user", name: "Kate Müller", age: 23, occupation: "Astronaut" }, + { type: "user", name: "Moses", age: 70, occupation: "Desert guide" }, + { type: "user", name: "Superman", age: 28, occupation: "Ordinary person" }, + { type: "user", name: "Inspector Gadget", age: 31, occupation: "Undercover" }, +]; + +function logUser(user: User | null) { + if (!user) { + console.log(" - none"); + return; + } + const pos = users.indexOf(user) + 1; + console.log(` - #${pos} User: ${user.name}, ${user.age}, ${user.occupation}`); +} + +function logAdmin(admin: Admin | null) { + if (!admin) { + console.log(" - none"); + return; + } + const pos = admins.indexOf(admin) + 1; + console.log(` - #${pos} Admin: ${admin.name}, ${admin.age}, ${admin.role}`); +} + +const compareUsers = (a: User, b: User) => a.age - b.age; +const compareAdmins = (a: Admin, b: Admin) => a.age - b.age; +const colorizeIndex = (value: number) => String(value + 1); + +export { + getMaxIndex, + getMaxElement, + getMinIndex, + getMinElement, + getMedianIndex, + getMedianElement, + getAverageValue, +}; + +console.log("Youngest user:"); +logUser(getMinElement(users, compareUsers)); +console.log( + ` - was ${colorizeIndex(getMinIndex(users, compareUsers))}th to register` +); + +console.log(); + +console.log("Median user:"); +logUser(getMedianElement(users, compareUsers)); +console.log( + ` - was ${colorizeIndex(getMedianIndex(users, compareUsers))}th to register` +); + +console.log(); + +console.log("Oldest user:"); +logUser(getMaxElement(users, compareUsers)); +console.log( + ` - was ${colorizeIndex(getMaxIndex(users, compareUsers))}th to register` +); + +console.log(); + +console.log("Average user age:"); +console.log( + ` - ${String(getAverageValue(users, ({ age }: User) => age))} years` +); + +console.log(); + +console.log("Youngest admin:"); +logAdmin(getMinElement(admins, compareAdmins)); +console.log( + ` - was ${colorizeIndex(getMinIndex(users, compareUsers))}th to register` +); + +console.log(); + +console.log("Median admin:"); +logAdmin(getMedianElement(admins, compareAdmins)); +console.log( + ` - was ${colorizeIndex(getMedianIndex(users, compareUsers))}th to register` +); + +console.log(); + +console.log("Oldest admin:"); +logAdmin(getMaxElement(admins, compareAdmins)); +console.log( + ` - was ${colorizeIndex(getMaxIndex(users, compareUsers))}th to register` +); + +console.log(); + +console.log("Average admin age:"); +console.log( + ` - ${String(getAverageValue(admins, ({ age }: Admin) => age))} years` +); +``` + +### 前置知识 + +- 泛型 +- 高阶函数 +- 如何给缺乏类型定义的第三方库定义类型 + +### 思路 + +和上面的思路类似。 唯一的不同的是这道题的需要实现的几个方法支持不同的入参类型。 + +```ts +import { + getMaxIndex, + getMaxElement, + getMinIndex, + getMinElement, + getMedianIndex, + getMedianElement, + getAverageValue, +} from "stats"; +``` + +因此,我们考虑使用泛型来定义。 知道了这个, 代码就不难写。 这是最最基本的泛型, 比我们前面写的还简单。 + +### 代码 + +```ts +declare module "stats" { + export function getMaxIndex( + input: T[], + comparator: (a: T, b: T) => number + ): number; + export function getMaxElement( + input: T[], + comparator: (a: T, b: T) => number + ): T; + export function getMinElement( + input: T[], + comparator: (a: T, b: T) => number + ): T; + export function getMedianIndex( + input: T[], + comparator: (a: T, b: T) => number + ): number; + export function getMedianElement( + input: T[], + comparator: (a: T, b: T) => number + ): T; + export function getAverageValue( + input: T[], + getValue: (a: T) => number + ): number; + export function getMinIndex( + input: T[], + comparator: (a: T, b: T) => number + ): number; +} +``` + +## 第十三题 + +### 题目描述 + +``` +Intro: + + The next logical step for us is to provide more + precise registration date for our users and admins. + We've approximately made up dates for each user and + admin and used a library called "date-wizard" in + order to pretty-format the dates. + + Unfortunately, type declarations which came with + "date-wizard" library were incomplete. + + 1. DateDetails interface is missing + time related fields such as hours, minutes and + seconds. + 2. Function "pad" is exported but not declared. + +Exercise: + + Check date-wizard module implementation at: + node_modules/date-wizard/index.js + node_modules/date-wizard/index.d.ts + + Extend type declaration of that module in: + module-augmentations/date-wizard/index.ts +``` + +题目大概意思是又来了一个库,这个库又没有写定义,我们又要自己写。 (真实+++++++++++++) + +### 题目内置代码 + +```ts +// module-augmentations/data-wizard/index.d.ts + +// This enables module augmentation mode. +import "date-wizard"; + +declare module "date-wizard" { + // Add your module extensions here. +} + +// index.ts +import * as dateWizard from "date-wizard"; +import "./module-augmentations/date-wizard"; + +interface User { + type: "user"; + name: string; + age: number; + occupation: string; + registered: Date; +} + +interface Admin { + type: "admin"; + name: string; + age: number; + role: string; + registered: Date; +} + +type Person = User | Admin; + +const admins: Admin[] = [ + { + type: "admin", + name: "Jane Doe", + age: 32, + role: "Administrator", + registered: new Date("2016-06-01T16:23:13"), + }, + { + type: "admin", + name: "Bruce Willis", + age: 64, + role: "World saver", + registered: new Date("2017-02-11T12:12:11"), + }, + { + type: "admin", + name: "Steve", + age: 40, + role: "Steve", + registered: new Date("2018-01-05T11:02:30"), + }, + { + type: "admin", + name: "Will Bruces", + age: 30, + role: "Overseer", + registered: new Date("2018-08-12T10:01:24"), + }, + { + type: "admin", + name: "Superwoman", + age: 28, + role: "Customer support", + registered: new Date("2019-03-25T07:51:05"), + }, +]; + +const users: User[] = [ + { + type: "user", + name: "Max Mustermann", + age: 25, + occupation: "Chimney sweep", + registered: new Date("2016-02-15T09:25:13"), + }, + { + type: "user", + name: "Kate Müller", + age: 23, + occupation: "Astronaut", + registered: new Date("2016-03-23T12:47:03"), + }, + { + type: "user", + name: "Moses", + age: 70, + occupation: "Desert guide", + registered: new Date("2017-02-19T17:22:56"), + }, + { + type: "user", + name: "Superman", + age: 28, + occupation: "Ordinary person", + registered: new Date("2018-02-25T19:44:28"), + }, + { + type: "user", + name: "Inspector Gadget", + age: 31, + occupation: "Undercover", + registered: new Date("2019-03-25T09:29:12"), + }, +]; + +const isAdmin = (person: Person): person is Admin => person.type === "admin"; +const isUser = (person: Person): person is User => person.type === "user"; + +function logPerson(person: Person, index: number) { + let additionalInformation: string = ""; + if (isAdmin(person)) { + additionalInformation = person.role; + } + if (isUser(person)) { + additionalInformation = person.occupation; + } + let registeredAt = dateWizard( + person.registered, + "{date}.{month}.{year} {hours}:{minutes}" + ); + let num = `#${dateWizard.pad(index + 1)}`; + console.log( + ` - ${num}: ${person.name}, ${person.age}, ${additionalInformation}, ${registeredAt}` + ); +} + +export { dateWizard }; + +console.log("All users:"); + +([] as Person[]).concat(users, admins).forEach(logPerson); + +console.log(); + +console.log("Early birds:"); + +([] as Person[]) + .concat(users, admins) + .filter((person) => dateWizard.dateDetails(person.registered).hours < 10) + .forEach(logPerson); + +// In case if you are stuck: +// https://www.typescriptlang.org/docs/handbook/modules.html#ambient-modules +// https://www.typescriptlang.org/docs/handbook/declaration-merging.html +``` + +### 前置知识 + +- interface 或 type 声明自定义类型 +- 如何给缺乏类型定义的第三方库定义类型 + +### 思路 + +和上面两道题思路一样, 不用多说了吧? + +### 代码 + +```ts +// This enables module augmentation mode. +import "date-wizard"; + +declare module "date-wizard" { + // Add your module extensions here. + function dateWizard(date: string, format: string): string; + function pad(s: number): string; + interface DateDetails { + year: number; + month: number; + date: number; + hours: number; + minutes: number; + seconds: number; + } + function dateDetails(date: Date): DateDetails; +} +``` + +## 第十四题 + +需要大家有函数式编程的知识, 如果大家不知道会比较难以解释。 为了避免内容太过分散,将这道题从我的题解中移除。 + +对函数式编程感兴趣的,也可是看下我之前写的文章 [函数式编程系列教程](https://github.com/azl397985856/functional-programming)。 + +## 第十五题 + +### 题目描述 + +``` +Intro: + + Our attempt to Open Source didn't work quite as + expected. It turned out there were already many + existing functional JS libraries. + + All the remaining developers left the company as + well. It seems that they are joining a very + ambitious startup which re-invented a juicer and + raised millions of dollars. + Too bad we cannot compete with this kind of + financing even though we believe our idea is + great. + + It's time to shine for the last time and publish + our new invention: object-constructor as our CTO + named it. A small library which helps + manipulating an object. + +Exercise: + + Here is a library which helps manipulating objects. + We tried to write type annotations and we failed. + Please help! +``` + +题目大概意思是函数式编程他们 hold 不住,于是又准备切换到面向对象编程。 于是你需要补充类型定义使得代码不报错。 + +### 题目内置代码 + +```ts +export class ObjectManipulator { + constructor(protected obj) {} + + public set(key, value) { + return new ObjectManipulator({ ...this.obj, [key]: value }); + } + + public get(key) { + return this.obj[key]; + } + + public delete(key) { + const newObj = { ...this.obj }; + delete newObj[key]; + return new ObjectManipulator(newObj); + } + + public getObject() { + return this.obj; + } +} +``` + +### 前置知识 + +- 泛型 +- Omit 泛型 +- ES6 class +- keyof +- 使用 extends 进行泛型约束 +- 联合类型 + +### 思路 + +这道题难度颇高,比前面的泛型题目都要难。 也是本系列的压轴题,我们重点讲一下。 + +首先题目有五个报错位置, 报错信息都是隐式使用了 any , 因此我们的思路就是将五个地方显式声明类型即可。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gjoo6olykmj30es0bbq3u.jpg) + +从它的名字 ObjectManipulator 以及 api 可以看出, 它应该可以存储任何对象,因此使用泛型定义就不难想到。 + +你也可是把这个 ObjectManipulator 想象成抽象包包。 你的期望是限量款包包拍照的时候用,普通包包和闺蜜逛街的时候用,优衣库送的包包逛超市的时候用等等。 + +ObjectManipulator 是一个抽象的包包概念,不是具体的包, 比如当你买一个 LV 的包包的时候就是 `ObjectManipulator`。这样当你往 LV 里放超市买的水果的时候就可以报错:`你怎么可以用 LV 包包装这样东西呢?你应该用 ta 装*`。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gjoq7nm6gnj30li0a6761.jpg) + +> 当然这个例子很不严谨, 这个只是帮助大家快速理解而已,切莫较真。 + +理解了题意,我们就可以开始写了。 + +我们先改第一个错 - 构造函数 constructor, 这个错比较简单。 + +```ts +export class ObjectManipulator { + constructor(protected obj: T) { + this.obj = obj; + } + ... +} +``` + +这个时候经过 ObjectManipulator 实例化产生的对象的 this.obj 都是 T 类型,其中 T 是泛型。因此 getObject 的错也不难改,返回值写 T 就行。 + +```ts +export class ObjectManipulator { + ... + public getObject(): T { + return this.obj; + } +} +``` + +剩下的 get,set 和 delete 思路有点类似。 先拿 get 来说: + +```ts +export class ObjectManipulator { + ... + public get(key) { + return this.obj[key]; + } + ... +} +``` + +这个怎么写类型呢? key 理论上可是是任何值,返回值理论上也可以是任何值。但是一旦类型 T 确定了, 那么实际上 key 和返回值就不是任意值了。 比如: + +```ts +type A = ObjectManipulator<{ name: string; age: number }>; +const a: A = new ObjectManipulator({ name: "", age: 17 }); +``` + +如上代码中的 A 是 ObjectManipulator 传入具体类型 `{ name: string; age: number }` 产生的新的类型。 + +> 我这里用的是行内类型, 实际项目建议使用 interface 或者 type 定义类型。 + +之后我们模拟一些操作: + +```ts +a.set("name", "脑洞前端"); +a.get("name"); +a.get("name123"); // 期望报错 +a.set("name123", "脑洞"); +a.delete("name123"); // 期望报错 +a.delete("name"); +``` + +实际上,我**可能**期望的是其中一些行为可以借助 TypeScript 的类型分析直接报错。 + +简单来说,我的期望是 **get 和 delete 不在 T 中的 key 都报错。** + +> 当然你的真实项目也可以不认同我的观点, 比如 get 一个不在 T 中定义的 key 也可以,但是我还是推荐你这么做。 + +知道了这个, 再结合我之前有关泛型的文章就不难写出来。 + +其中 get 和 delete 的代码: + +```ts +export class ObjectManipulator { + public get(key: K): T[K] { + return this.obj[key]; + } + + public delete(key: K): ObjectManipulator> { + const newObj = { ...this.obj }; + delete newObj[key]; + return new ObjectManipulator(newObj); + } +} +``` + +最后是 set,其实一开始我的 set 是这么写的。 + +```ts +export class ObjectManipulator { + public set(key: K, value: V): ObjectManipulator { + return new ObjectManipulator({ + ...this.obj, + [key]: value, + }) as ObjectManipulator; + } +} +``` + +但是无奈没有通过官方的测试用例。 实际项目我其实更推荐我上面的这种写法。下面是我为了通过所有的测试用例写的方法。 + +经过分析, 我发现它期望的是 set 中的 key 可以不是 T 中的。这一点从官方给的测试用例就可以看出来。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gjoowz893ej30cp040wet.jpg) + +因此我将代码改成 K 放宽到任意 string,返回值做了一个联合类型。代码: + +```ts +export class ObjectManipulator { + ... + public set( + key: K, + value: V + ): ObjectManipulator { + return new ObjectManipulator({ + ...this.obj, + [key]: value, + }) as ObjectManipulator; + } + ... +} +``` + +终于通过了所有的测试用例。 + +### 代码 + +```ts +export class ObjectManipulator { + constructor(protected obj: T) { + this.obj = obj; + } + public set( + key: K, + value: V + ): ObjectManipulator { + return new ObjectManipulator({ + ...this.obj, + [key]: value, + }) as ObjectManipulator; + } + + public get(key: K): T[K] { + return this.obj[key]; + } + + public delete(key: K): ObjectManipulator> { + const newObj = { ...this.obj }; + delete newObj[key]; + return new ObjectManipulator(newObj); + } + + public getObject(): T { + return this.obj; + } +} +``` + +## 总结 + +以上就是给大家带来的题目解析。 这六道题的考点有,按照我个人理解的重要程度划分为: + +- type 和 interface 的基本操作(必须掌握) +- 如何给缺乏类型定义的第三方库定义类型(必须掌握) +- 联合类型 和 交叉类型(强烈建议掌握) +- 类型断言和类型收缩(强烈建议掌握) +- 泛型和常见内置泛型(强烈建议掌握) +- 高阶函数的类型定义(强烈建议掌握) + +最后祝愿大家告别 anyscript,成为 TypeScript 魔法师。 + +## 关注我 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) + +公众号【 [力扣加加](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)】 +知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 + +点关注,不迷路! diff --git a/docs/topics/ts/ts-exercises.md b/docs/topics/ts/ts-exercises.md new file mode 100644 index 0000000..14895ee --- /dev/null +++ b/docs/topics/ts/ts-exercises.md @@ -0,0 +1,1031 @@ +--- +title: TypeScript 练习题 +tags: [前端, TypeScript] +date: 2020-09-27 +categories: + - [前端, TypeScript] +--- + +TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: + +- 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在**逻辑上**比较零散。 +- 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 +- 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 + +因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 + +系列安排: + +- [上帝视角看 TypeScript](https://lucifer.ren/blog/2020/08/04/ts-internal/) +- [TypeScript 类型系统](https://lucifer.ren/blog/2020/08/15/ts-type-system/) +- [types 和 @types 是什么?](https://lucifer.ren/blog/2020/08/21/ts-type/) +- [你不知道的 TypeScript 泛型(万字长文,建议收藏)](https://lucifer.ren/blog/2020/06/16/ts-generics/) +- [TypeScript 配置文件该怎么写?](https://lucifer.ren/blog/2020/08/24/ts-config/) +- TypeScript 是如何与 React,Vue,Webpack 集成的? +- TypeScript 练习题(就是本文) + +> 目录将来可能会有所调整。 + +注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 + +- [深入理解 TypeScript](https://jkchao.github.io/typescript-book-chinese/ "深入理解 TypeScript") +- [TypeScript 官方文档](https://www.typescriptlang.org/docs/home "TypeScript 官方文档") + +结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 + +接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 + + + +## 前言 + +本文涉及的题目一共十五道,全部都可以在 [typescript-exercises](https://typescript-exercises.github.io/ "typescript-exercises") 上在线提交。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj1sl6j7pij31560p4ad7.jpg) + +可以和标准答案进行对比。 +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj1slk6r7bj31g30o9djp.jpg) + +并且由于使用了浏览器缓存, 因此无需登录的情况下也可以保证关掉页面,你的答题进度也会保留。 + +> 想重置进度,清空缓存,无痕模式或者换浏览器都可以。 + +题目中涉及到的知识点我基本也都在之前的文章中提到了,如果你没有看过,强烈建议先完成前面的教程,然后将上面的题目自己做一遍之后再看本文。另外一定要按照顺序读, 因此前面的题目都是后面的铺垫。 + +为了不让文章太过于冗长, 本篇文章分两次发布, 这一次是 8 道题,一共十五道。每道题都有思路,前置知识以及代码。 + +## 题目一 + +### 题目描述 + +``` +Intro: + + We are starting a small community of users. For performance + reasons we have decided to store all users right in the code. + This way we can provide our developers with more + user-interaction opportunities. With user-related data, at least. + All the GDPR-related issues we will solved some other day. + This would be the base for our future experiments during + these exercises. + +Exercise: + + Given the data, define the interface "User" and use it accordingly. + +``` + +题目的大概意思是让你定义一个类型 `User`, 使得代码可以正常运行。 + +### 题目内置代码 + +```ts +export type User = unknown; + +export const users: unknown[] = [ + { + name: "Max Mustermann", + age: 25, + occupation: "Chimney sweep", + }, + { + name: "Kate Müller", + age: 23, + occupation: "Astronaut", + }, +]; + +export function logPerson(user: unknown) { + console.log(` - ${user.name}, ${user.age}`); +} + +console.log("Users:"); +users.forEach(logPerson); +``` + +### 前置知识 + +- interface 或 type 声明自定义类型 + +### 思路 + +这道题比较简单, 我们只有定义一个 User 类即可。从 users 数组中不难看出, User 中有三个属性 name ,age 和 occupation,类型分别为 string, number 和 string。因此直接使用 type 或者 interface 定义自定义类型即可。 + +### 代码 + +核心代码: + +```ts +export type User = { + name: string; + age: number; + occupation: string; +}; +``` + +## 题目二 + +### 题目描述 + +``` +Intro: + + All 2 users liked the idea of the community. We should go + forward and introduce some order. We are in Germany after all. + Let's add a couple of admins. + + Initially we only had users in the in-memory database. After + introducing Admins, we need to fix the types so that + everything works well together. + +Exercise: + + Type "Person" is missing, please define it and use + it in persons array and logPerson function in order to fix + all the TS errors. +``` + +题目大意是补充 Person 类, 使得代码不报错。 + +### 题目内置代码 + +```ts +interface User { + name: string; + age: number; + occupation: string; +} + +interface Admin { + name: string; + age: number; + role: string; +} + +export type Person = unknown; + +export const persons: User[] /* <- Person[] */ = [ + { + name: "Max Mustermann", + age: 25, + occupation: "Chimney sweep", + }, + { + name: "Jane Doe", + age: 32, + role: "Administrator", + }, + { + name: "Kate Müller", + age: 23, + occupation: "Astronaut", + }, + { + name: "Bruce Willis", + age: 64, + role: "World saver", + }, +]; + +export function logPerson(user: User) { + console.log(` - ${user.name}, ${user.age}`); +} + +persons.forEach(logPerson); +``` + +### 前置知识 + +- 联合类型 + +### 思路 + +我们直接从报错入手。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj1xl8b8exj30ph0bhgmp.jpg) + +不难发现 persons 数组既有 User 又有 Admin。 因此 person 的函数签名应该是两者的联合类型。而题目又让我们补充 Person,于是代码将 Person 定义为 Admin 和 User 的联合类型就不难想到。 + +### 代码 + +核心代码: + +```ts +export type Person = User | Admin; +``` + +这个时候, persons 数组使用的过程只能用 User 和 Admin 的共有属性, 也就是 name 和 age,这点后面的题目也会提到。 因此如果你使用了 role 或者 occupation 就会报错。怎么解决呢? 我们继续看下一题。 + +## 第三题 + +### 题目描述 + +``` +Intro: + + Since we already have some of the additional + information about our users, it's a good idea + to output it in a nice way. + +Exercise: + + Fix type errors in logPerson function. + + logPerson function should accept both User and Admin + and should output relevant information according to + the input: occupation for User and role for Admin. +``` + +### 题目内置代码 + +```ts +interface User { + name: string; + age: number; + occupation: string; +} + +interface Admin { + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { + name: "Max Mustermann", + age: 25, + occupation: "Chimney sweep", + }, + { + name: "Jane Doe", + age: 32, + role: "Administrator", + }, + { + name: "Kate Müller", + age: 23, + occupation: "Astronaut", + }, + { + name: "Bruce Willis", + age: 64, + role: "World saver", + }, +]; + +export function logPerson(person: Person) { + let additionalInformation: string; + if (person.role) { + additionalInformation = person.role; + } else { + additionalInformation = person.occupation; + } + console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`); +} + +persons.forEach(logPerson); +``` + +### 前置知识 + +- 类型断言 +- 类型收敛 +- in 操作符 + +### 思路 + +关于类型收敛, 我在 [TypeScript 类型系统](https://lucifer.ren/blog/2020/08/15/ts-type-system/) 做了很详情的讨论。 + +上面代码报错的原因前面已经讲过了, 那么如何解决呢?由于 person 可能是 User ,也可能是 Admin 类型,而 TypeScript 没有足够的信息确定具体是哪一种。因此你使用 User 或者 Admin `特有`的属性就会报错了。 + +因此解决方案的基本思想就是告诉 TypeScript **person 当前是 Admin 还是 User 类型**。有多种方式可以解决这个问题。 + +1. 将 person 断言为准确的类型。 就是告诉 TypeScript ”交给我吧, person 就是 xxx 类型,有错就我的锅“。 + +代码: + +```ts +if ((person).role) { + additionalInformation = (person).role; +} else { + additionalInformation = (person).occupation; +} +``` + +2. 另外一种方式是使用类型收缩,比如 is , in, typeof , instanceof 等。使得 Typescript 能够 Get 到当前的类型。”哦, person 上有 role 属性啊,那它就是 Admin 类型,有问题我 Typescript 的锅“ + +这里我们使用 in 操作符,写起来也很简单。 + +> 推荐哪种不用我多说了吧 ? + +### 代码 + +```ts +if ("role" in person) { + // person 会被自动推导为 Admin + additionalInformation = person.role; +} else { + // Person 会被自动推导为 User + additionalInformation = person.occupation; +} +``` + +## 第四题 + +### 题目描述 + +``` +Intro: + + As we introduced "type" to both User and Admin + it's now easier to distinguish between them. + Once object type checking logic was extracted + into separate functions isUser and isAdmin - + logPerson function got new type errors. + +Exercise: + + Figure out how to help TypeScript understand types in + this situation and apply necessary fixes. +``` + +大概意思还是让你改代码, 使得 Typescript 能理解(不报错)。 + +### 题目内置代码 + +```ts +interface User { + type: "user"; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: "admin"; + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { + type: "user", + name: "Max Mustermann", + age: 25, + occupation: "Chimney sweep", + }, + { type: "admin", name: "Jane Doe", age: 32, role: "Administrator" }, + { type: "user", name: "Kate Müller", age: 23, occupation: "Astronaut" }, + { type: "admin", name: "Bruce Willis", age: 64, role: "World saver" }, +]; + +export function isAdmin(person: Person) { + return person.type === "admin"; +} + +export function isUser(person: Person) { + return person.type === "user"; +} + +export function logPerson(person: Person) { + let additionalInformation: string = ""; + if (isAdmin(person)) { + additionalInformation = person.role; + } + if (isUser(person)) { + additionalInformation = person.occupation; + } + console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`); +} + +console.log("Admins:"); +persons.filter(isAdmin).forEach(logPerson); + +console.log(); + +console.log("Users:"); +persons.filter(isUser).forEach(logPerson); +``` + +### 前置知识 + +- 类型收敛 +- is 操作符 + +### 思路 + +我们仍然从报错入手。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj1zgdvcxxj30n608cta2.jpg) + +实际上还是 person 的类型问题, 没有被收缩到正确的类型。看题目的代码,期望效果应该是`如果进入 isAdmin 内部,那么 person 就是 Admin 类型,同理进入 isUser 内部,那么 person 就是 User 类型。` + +继续看下 isAdmin 和 isUser 的实现: + +```ts +export function isAdmin(person: Person) { + return person.type === "admin"; +} + +export function isUser(person: Person) { + return person.type === "user"; +} +``` + +这里我们期望的效果是如果 isAdmin 函数返回 true ,那么 person 就应该被收敛为 Admin,isUser 同理。 + +这里就需要用到 is 操作符。 + +> 上文提到了类型收敛常见的操作符是 is , in, typeof , instanceof + +### 代码 + +```ts +export function isAdmin(person: Person): person is Admin { + return person.type === "admin"; +} + +export function isUser(person: Person): person is User { + return person.type === "user"; +} +``` + +这样当 isAdmin 返回 true, 那么 person 变量就会被推导成 Admin 类型,而不是联合类型, 也就是类型发生了收缩。 + +不难看出,这样的类型断言会直接影响到调用 isAdmin 或 isUser 的**函数的入参的类型**。 + +## 第五题 + +### 题目描述 + +``` +Intro: + + Time to filter the data! In order to be flexible + we filter users using a number of criteria and + return only those matching all of the criteria. + We don't need Admins yet, we only filter Users. + +Exercise: + + Without duplicating type structures, modify + filterUsers function definition so that we can + pass only those criteria which are needed, + and not the whole User information as it is + required now according to typing. + +Higher difficulty bonus exercise: + + Exclude "type" from filter criterias. +``` + +大概意思是让你改 filterUsers, 但要注意 `DRY`(Don't Repeat Yourself)。 + +### 题目内置代码 + +```ts +interface User { + type: "user"; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: "admin"; + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { + type: "user", + name: "Max Mustermann", + age: 25, + occupation: "Chimney sweep", + }, + { + type: "admin", + name: "Jane Doe", + age: 32, + role: "Administrator", + }, + { + type: "user", + name: "Kate Müller", + age: 23, + occupation: "Astronaut", + }, + { + type: "admin", + name: "Bruce Willis", + age: 64, + role: "World saver", + }, + { + type: "user", + name: "Wilson", + age: 23, + occupation: "Ball", + }, + { + type: "admin", + name: "Agent Smith", + age: 23, + role: "Administrator", + }, +]; + +export const isAdmin = (person: Person): person is Admin => + person.type === "admin"; +export const isUser = (person: Person): person is User => + person.type === "user"; + +export function logPerson(person: Person) { + let additionalInformation = ""; + if (isAdmin(person)) { + additionalInformation = person.role; + } + if (isUser(person)) { + additionalInformation = person.occupation; + } + console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`); +} + +export function filterUsers(persons: Person[], criteria: User): User[] { + return persons.filter(isUser).filter((user) => { + const criteriaKeys = Object.keys(criteria) as (keyof User)[]; + return criteriaKeys.every((fieldName) => { + return user[fieldName] === criteria[fieldName]; + }); + }); +} + +console.log("Users of age 23:"); + +filterUsers(persons, { + age: 23, +}).forEach(logPerson); +``` + +### 前置知识 + +- 泛型 +- Partial 泛型 + +### 思路 + +老规矩, 从报错入手。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj1zotdia3j30q105xwf5.jpg) + +大概意思是 { age: 23 } 不完整,缺失了部分 key。而题目实际上的想法应该是想根据部分内容对人员进行检错。比如可以根据 age 查, 也可以根据 name 查,也可以同时根据 age 和 name 查等,这和我们平时的搜索逻辑是一致的。 + +直接用 Partial 泛型即可解决, 不懂的可以看下我的文章[你不知道的 TypeScript 泛型(万字长文,建议收藏)](https://lucifer.ren/blog/2020/06/16/ts-generics/)。 + +### 代码 + +```ts +export function filterUsers(persons: Person[], criteria: Partial): User[] { + ... +} +``` + +## 第六题 + +### 题目描述 + +``` +Intro: + + Filtering requirements have grown. We need to be + able to filter any kind of Persons. + +Exercise: + + Fix typing for the filterPersons so that it can filter users + and return User[] when personType='user' and return Admin[] + when personType='admin'. Also filterPersons should accept + partial User/Admin type according to the personType. + `criteria` argument should behave according to the + `personType` argument value. `type` field is not allowed in + the `criteria` field. + +Higher difficulty bonus exercise: + + Implement a function `getObjectKeys()` which returns more + convenient result for any argument given, so that you don't + need to cast it. + + let criteriaKeys = Object.keys(criteria) as (keyof User)[]; + --> + let criteriaKeys = getObjectKeys(criteria); +``` + +大概意思是让你改 filterUsers, 但要注意 `DRY`(Don't Repeat Yourself)。并且可以根据 personType 的不同,返回不同的类型。 + +### 题目内置代码 + +```ts +interface User { + type: "user"; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: "admin"; + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { + type: "user", + name: "Max Mustermann", + age: 25, + occupation: "Chimney sweep", + }, + { type: "admin", name: "Jane Doe", age: 32, role: "Administrator" }, + { type: "user", name: "Kate Müller", age: 23, occupation: "Astronaut" }, + { type: "admin", name: "Bruce Willis", age: 64, role: "World saver" }, + { type: "user", name: "Wilson", age: 23, occupation: "Ball" }, + { type: "admin", name: "Agent Smith", age: 23, role: "Anti-virus engineer" }, +]; + +export function logPerson(person: Person) { + console.log( + ` - ${person.name}, ${person.age}, ${ + person.type === "admin" ? person.role : person.occupation + }` + ); +} + +export function filterPersons( + persons: Person[], + personType: string, + criteria: unknown +): unknown[] { + return persons + .filter((person) => person.type === personType) + .filter((person) => { + let criteriaKeys = Object.keys(criteria) as (keyof Person)[]; + return criteriaKeys.every((fieldName) => { + return person[fieldName] === criteria[fieldName]; + }); + }); +} + +export const usersOfAge23 = filterPersons(persons, "user", { age: 23 }); +export const adminsOfAge23 = filterPersons(persons, "admin", { age: 23 }); + +console.log("Users of age 23:"); +usersOfAge23.forEach(logPerson); + +console.log(); + +console.log("Admins of age 23:"); +adminsOfAge23.forEach(logPerson); +``` + +### 前置知识 + +- 泛型 +- Partial 泛型 +- 函数重载 + +### 思路 + +题目描述也懒得看了, 直接看报错。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj1zvhq5vpj30xq0am40w.jpg) + +报错信息提示我们没有找到合适的函数重载。 因此我的思路就是补上合适的重载即可。关于函数重载,我的系列教程不涉及,大家可以看下官网资料。 + +重载之后,不同的情况调用返回值就可以对应不同的类型。本题中就是: + +- 如果 personType 是 admin,就会返回 Admin 数组。 +- 如果 personType 是 user,就会返回 User 数组。 +- 如果 personType 是其他 string,就会返回 Person 数组。 + +### 代码 + +```ts +export function filterPersons(persons: Person[], personType: 'admin', criteria: Partial): Admin[] +export function filterPersons(persons: Person[], personType: 'user', criteria: Partial): User[] +export function filterPersons(persons: Person[], personType: string, criteria: Partial): Person[] { + ... +} + +``` + +## 第七题 + +### 题目描述 + +``` +Intro: + + Filtering was completely removed from the project. + It turned out that this feature was just not needed + for the end-user and we spent a lot of time just because + our office manager told us to do so. Next time we should + instead listen to the product management. + + Anyway we have a new plan. CEO's friend Nick told us + that if we randomly swap user names from time to time + in the community, it would be very funny and the project + would definitely succeed! + +Exercise: + + Implement swap which receives 2 persons and returns them in + the reverse order. The function itself is already + there, actually. We just need to provide it with proper types. + Also this function shouldn't necessarily be limited to just + Person types, lets type it so that it works with any two types + specified. +``` + +题目大概意思是让你修改 swap 函数,使得不报错。 并且,我希望这个函数可以适用于任意两个变量,不管其类型一样不一样, 也不管二者类型是什么。 + +### 题目内置代码 + +```ts +interface User { + type: "user"; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: "admin"; + name: string; + age: number; + role: string; +} + +function logUser(user: User) { + const pos = users.indexOf(user) + 1; + console.log(` - #${pos} User: ${user.name}, ${user.age}, ${user.occupation}`); +} + +function logAdmin(admin: Admin) { + const pos = admins.indexOf(admin) + 1; + console.log(` - #${pos} Admin: ${admin.name}, ${admin.age}, ${admin.role}`); +} + +const admins: Admin[] = [ + { + type: "admin", + name: "Will Bruces", + age: 30, + role: "Overseer", + }, + { + type: "admin", + name: "Steve", + age: 40, + role: "Steve", + }, +]; + +const users: User[] = [ + { + type: "user", + name: "Moses", + age: 70, + occupation: "Desert guide", + }, + { + type: "user", + name: "Superman", + age: 28, + occupation: "Ordinary person", + }, +]; + +export function swap(v1, v2) { + return [v2, v1]; +} + +function test1() { + console.log("test1:"); + const [secondUser, firstAdmin] = swap(admins[0], users[1]); + logUser(secondUser); + logAdmin(firstAdmin); +} + +function test2() { + console.log("test2:"); + const [secondAdmin, firstUser] = swap(users[0], admins[1]); + logAdmin(secondAdmin); + logUser(firstUser); +} + +function test3() { + console.log("test3:"); + const [secondUser, firstUser] = swap(users[0], users[1]); + logUser(secondUser); + logUser(firstUser); +} + +function test4() { + console.log("test4:"); + const [firstAdmin, secondAdmin] = swap(admins[1], admins[0]); + logAdmin(firstAdmin); + logAdmin(secondAdmin); +} + +function test5() { + console.log("test5:"); + const [stringValue, numericValue] = swap(123, "Hello World"); + console.log(` - String: ${stringValue}`); + console.log(` - Numeric: ${numericValue}`); +} + +[test1, test2, test3, test4, test5].forEach((test) => test()); +``` + +### 前置知识 + +- 泛型 + +### 思路 + +题目废话很多, 直接忽略看报错。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj201q6ov0j30i104i74m.jpg) + +这个其实我在 [你不知道的 TypeScript 泛型(万字长文,建议收藏)](https://lucifer.ren/blog/2020/06/16/ts-generics/) 里也讲过了,直接看代码。 + +### 代码 + +```ts +export function swap(v1: T, v2: U): [U, T] { + return [v2, v1]; +} +``` + +## 第八题 + +### 题目描述 + +``` +Intro: + + Project grew and we ended up in a situation with + some users starting to have more influence. + Therefore, we decided to create a new person type + called PowerUser which is supposed to combine + everything User and Admin have. + +Exercise: + + Define type PowerUser which should have all fields + from both User and Admin (except for type), + and also have type 'powerUser' without duplicating + all the fields in the code. +``` + +题目大概意思是定义一个类型 PowerUser, 里面包含 User 和 Admin 的所有属性, 并且有一个字段是固定的 type: 'powerUser'。 + +### 题目内置代码 + +```ts +interface User { + type: "user"; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: "admin"; + name: string; + age: number; + role: string; +} + +type PowerUser = unknown; + +export type Person = User | Admin | PowerUser; + +export const persons: Person[] = [ + { + type: "user", + name: "Max Mustermann", + age: 25, + occupation: "Chimney sweep", + }, + { type: "admin", name: "Jane Doe", age: 32, role: "Administrator" }, + { type: "user", name: "Kate Müller", age: 23, occupation: "Astronaut" }, + { type: "admin", name: "Bruce Willis", age: 64, role: "World saver" }, + { + type: "powerUser", + name: "Nikki Stone", + age: 45, + role: "Moderator", + occupation: "Cat groomer", + }, +]; + +function isAdmin(person: Person): person is Admin { + return person.type === "admin"; +} + +function isUser(person: Person): person is User { + return person.type === "user"; +} + +function isPowerUser(person: Person): person is PowerUser { + return person.type === "powerUser"; +} + +export function logPerson(person: Person) { + let additionalInformation: string = ""; + if (isAdmin(person)) { + additionalInformation = person.role; + } + if (isUser(person)) { + additionalInformation = person.occupation; + } + if (isPowerUser(person)) { + additionalInformation = `${person.role}, ${person.occupation}`; + } + console.log(`${person.name}, ${person.age}, ${additionalInformation}`); +} + +console.log("Admins:"); +persons.filter(isAdmin).forEach(logPerson); + +console.log(); + +console.log("Users:"); +persons.filter(isUser).forEach(logPerson); + +console.log(); + +console.log("Power users:"); +persons.filter(isPowerUser).forEach(logPerson); +``` + +### 前置知识 + +- 集合操作(交叉类型) +- & 操作符 +- 泛型 +- Omit 泛型 + +### 思路 + +从题目信息不难看出,就是让我们实现 PowerUser。 + +有前面的分析不难得出我们只需要: + +- 合并 User 和 Admin 的属性即可。 借助 & 操作符可以实现。即 `User & Admin`。 +- 增加特有的属性 type: powerUser。 首先去掉上一步合并的 type 属性, 然后继续和 { type: "powerUser" } 交叉即可。 +- 增加 { type: "powerUser" } 之前使用内置泛型 Omit 将原本的 type 删掉即可。 + +### 代码 + +```ts +type PowerUser = Omit & { type: "powerUser" }; +``` + +## 总结 + +以上就是给大家带来的题目解析。 这八道题的考点有,按照我个人理解的重要程度划分为: + +- type 和 interface 的基本操作(必须掌握) +- 联合类型 和 交叉类型(强烈建议掌握) +- 类型断言和类型收缩(强烈建议掌握) +- 泛型和常见内置泛型(强烈建议掌握) +- 函数重载(推荐掌握) + +最后祝愿大家告别 anyscript,成为 TypeScript 魔法师。 + +## 关注我 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) + +公众号【 [力扣加加](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)】 +知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 + +点关注,不迷路! diff --git a/docs/topics/ts/ts-generics.md b/docs/topics/ts/ts-generics.md new file mode 100644 index 0000000..d748653 --- /dev/null +++ b/docs/topics/ts/ts-generics.md @@ -0,0 +1,758 @@ +--- +title: 你不知道的 TypeScript 泛型(万字长文,建议收藏) +tags: [前端, TypeScript, 泛型] +categories: [前端, TypeScript, 泛型] +--- + +泛型是 TypeScript(以下简称 TS) 比较高级的功能之一,理解起来也比较困难。泛型应用场景非常广泛,很多地方都能看到它的影子。平时我们阅读开源 TS 项目源码,或者在自己的 TS 项目中使用一些第三方库(比如 React)的时候,经常会看到各种泛型定义。如果你不是特别了解泛型,那么你很可能不仅不会用,不会实现,甚至看不懂这是在干什么。 + +相信大家都经历过,看到过,或者正在写**一些应用,这些应用充斥着各种重复类型定义, any 类型层出不穷,鼠标移到变量上面的提示只有 any,不要说类型操作了,类型能写对都是个问题**。我也经历过这样的阶段,那个时候我对 TS 还比较陌生。 + +随着在 TS 方面学习的深入,越来越认识到 **真正的 TS 高手都是在玩类型**,对类型进行各种运算生成新的类型。这也好理解,毕竟 **TS 提供的其实就是类型系统**。你去看那些 TS 高手的代码,会各种**花式使用泛型**。 可以说泛型是一道坎,只有真正掌握它,你才知道**原来 TS 还可以这么玩**。怪不得面试的时候大家都愿意问泛型,尽管面试官很可能也不怎么懂。 + +**只有理解事物的内在逻辑,才算真正掌握了,不然永远只是皮毛,不得其法**。 本文就带你走进泛型,带你从另一个角度看看究竟什么是泛型,为什么要有它,它给 TS 带来了什么样的不同。 + +> 注意:不同语言泛型略有不同,知识迁移虽然可以,但是不能生搬硬套,本文所讲的泛型都指的是 TS 下的泛型。 + + + +## 引言 + +我总结了一下,学习 TS 有两个难点。第一个是`TS 和 JS 中容易混淆的写法`,第二个是`TS中特有的一些东西`。 + +- TS 中容易引起大家的混淆的写法 + +比如: + +![容易混淆的箭头函数](https://tva1.sinaimg.cn/large/007S8ZIlly1gfwlko8ermj310x0fx77r.jpg) + +(容易混淆的箭头函数) + +再比如: + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfwlnifyi5j311j0ao0vw.jpg) + +(容易混淆的 interface 内的小括号) + +- TS 中特有的一些东西 + +比如 typeof,keyof, infer 以及本文要讲的泛型。 + +**把这些和 JS 中容易混淆的东西分清楚,然后搞懂 TS 特有的东西,尤其是泛型**(其他基本上相对简单),TS 就入门了。 + +### 泛型初体验 + +在强类型语言中,一般而言需要给变量指定类型才能使用该变量。如下代码: + +```ts +const name: string = "lucifer"; +console.log(name); +``` + +我们需要给 name 声明 string 类型,然后才能在后面使用 name 变量,当我们执行以下操作的时候会报错。 + +- 给 name 赋其他类型的值 +- 使用其他类型值特有的方法(比如 Number 类型特有的 toFixed) +- 将 name 以参数传给不支持 string 的函数。 比如 `divide(1, name)`,其中 divide 就是功能就是`将第一个数(number 类型)除以第二个数(number 类型),并将结果返回`。 + +TS 除了提供一些基本类型(比如上面的 string)供我们直接使用。还: + +- 提供了 `inteface` 和 `type` 关键字供我们定义自己的类型,之后就能像使用基本类型一样使用自己定义的类型了。 +- 提供了各种逻辑运算符,比如 &, | 等 ,供我们对类型进行操作,从而生成新的类型。 +- 提供泛型,允许我们在定义的时候不具体指定类型,而是泛泛地说一种类型,并在函数调用的时候再指定具体的参数类型。 +- 。。。 + +也就是说泛型也是一种类型,只不过不同于 string, number 等具体的类型,它是一种抽象的类型,我们不能直接定义一个变量类型为泛型。 + +简单来说,区别于平时我们对**值**进行编程,泛型是对**类型**进行编程。这个听起来比较抽象。之后我们会通过若干实例带你理解这句话,你先留一个印象就好。 + +为了明白上面这句话,·首先要区分“值”和“类型”。 + +### 值和类型 + +我们平时写代码基本都是**对值编程**。比如: + +```js +if (person.isVIP) { + console.log('VIP') +} +if (cnt > 5) { + // do something +} + +const personNames = persons.map(p => p.name) +... +``` + +可以看出这都是对具体的值进行编程,**这符合我们对现实世界的抽象**。从集合论的角度上来说, 值的集合就是类型,在 TS 中最简单的用法是对值限定类型,从根本上来说是限定值的集合。这个集合可以是一个具体的集合,也可以是多个集合通过集合运算(交叉并)生成的新集合。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfvq6o3iwcj32960su42q.jpg) + +(值和类型) + +再来看一个更具体的例子: + +```ts +function t(name: string) { + return `hello, ${name}`; +} +t("lucifer"); +``` + +字符串 "lucifer" 是 string **类型**的一个具体**值**。 在这里 "lucifer" 就是值,而 string 就是类型。 + +TS 明白 "lucifer" 是 string 集合中的一个元素,因此上面代码不会有问题,但是如果是这样就会报错: + +```ts +t(123); +``` + +因为 123 并不是 string 集合中的一个元素。 + +对于 t("lucifer")而言,TS 判断逻辑的伪代码: + +```js +v = getValue(); // will return 'lucifer' by ast +if (typeof v === "string") { + // ok +} else { + throw "type error"; +} +``` + +> 由于是静态类型分析工具,因此 TS 并不会执行 JS 代码,但并不是说 TS 内部没有执行逻辑。 + +简单来总结一下就是: 值的集合就是类型,平时写代码基本都是对值编程,TS 提供了很多**类型**(也可以自定义)以及很多**类型操作**帮助我们**限定值以及对值的操作**。 + +## 什么是泛型 + +上面已经铺垫了一番,大家已经知道了值和类型的区别,以及 TS 究竟帮我们做了什么事情。但是直接理解泛型仍然会比较吃力,接下来我会通过若干实例,慢慢带大家走进泛型。 + +首先来思考一个问题:`为什么要有泛型呢`?这个原因实际上有很多,在这里我选择大家普遍认同的一个切入点来解释。如果你明白了这个点,其他点相对而言理解起来会比较轻松。还是通过一个例子来进行说明。 + +### 不容小觑的 id 函数 + +假如让你实现一个函数 `id`,函数的参数可以是任何值,返回值就是将参数原样返回,并且其只能接受一个参数,你会怎么做? + +你会觉得这很简单,顺手就写出这样的代码: + +```js +const id = (arg) => arg; +``` + +> 有的人可能觉得 id 函数没有什么实际作用。其实不然, id 函数在函数式编程中应用非常广泛。 + +由于其可以接受任意值,也就是说你的函数的入参和返回值都应该可以是任意类型。 现在让我们给代码增加类型声明: + +```js +type idBoolean = (arg: boolean) => boolean; +type idNumber = (arg: number) => number; +type idString = (arg: string) => string; +... +``` + +一个笨的方法就像上面那样,也就是说 JS 提供多少种类型,就需要复制多少份代码,然后改下类型签名。这对程序员来说是致命的。这种复制粘贴增加了出错的概率,使得代码难以维护,牵一发而动全身。并且将来 JS 新增新的类型,你仍然需要修改代码,也就是说你的代码**对修改开放**,这样不好。还有一种方式是使用 any 这种“万能语法”。缺点是什么呢?我举个例子: + +```js +id("string").length; // ok +id("string").toFixed(2); // ok +id(null).toString(); // ok +... +``` + +如果你使用 any 的话,怎么写都是 ok 的, 这就丧失了类型检查的效果。实际上我知道我传给你的是 string,返回来的也一定是 string,而 string 上没有 toFixed 方法,因此需要报错才是我想要的。也就是说我真正想要的效果是:`当我用到id的时候,你根据我传给你的类型进行推导`。比如我传入的是 string,但是使用了 number 上的方法,你就应该报错。 + +为了解决上面的这些问题,我们**使用泛型对上面的代码进行重构**。和我们的定义不同,这里用了一个 类型 T,这个 **T 是一个抽象类型,只有在调用的时候才确定它的值**,这就不用我们复制粘贴无数份代码了。 + +```js +function id(arg: T): T { + return arg; +} +``` + +为什么这样就可以了? 为什么要用这种写法?这个尖括号什么鬼?万物必有因果,之所以这么设计泛型也是有原因的。那么就让我来给大家解释一下,相信很多人都没有从这个角度思考过这个问题。 + +### 泛型就是对类型编程 + +上面提到了一个重要的点 `平时我们都是对值进行编程,泛型是对类型进行编程`。上面我没有给大家解释这句话。现在铺垫足够了,那就让我们开始吧! + +继续举一个例子:假如我们定义了一个 Person 类,这个 Person 类有三个属性,并且都是必填的。这个 Person 类会被用于用户提交表单的时候限定表单数据。 + +```ts +enum Sex { + Man, + Woman, + UnKnow, +} +interface Person { + name: string; + sex: Sex; + age: number; +} +``` + +突然有一天,公司运营想搞一个促销活动,也需要用到 Person 这个 `shape`,但是这三个属性都可以选填,同时要求用户必须填写手机号以便标记用户和接受短信。一个很笨的方法是重新写一个新的类: + +```ts +interface MarketPerson { + name?: string; + sex?: Sex; + age?: number; + phone: string; +} +``` + +> 还记得我开头讲的重复类型定义么? 这就是! + +这明显不够优雅。如果 Person 字段很多呢?这种重复代码会异常多,不利于维护。 TS 的设计者当然不允许这么丑陋的设计存在。那么是否可以根据已有类型,生成新的类型呢?当然可以!答案就是前面我提到了两种对类型的操作:**一种是集合操作,另一种是今天要讲的泛型。** + +先来看下集合操作: + +```ts +type MarketPerson = Person & { phone: string }; +``` + +这个时候我们虽然添加了一个必填字段 phone,但是没有做到`name, sex, age` 选填,似乎集合操作做不到这一点呀。我们脑洞一下,假如我们可以**像操作函数那样操作类型**,是不是有可能呢?比如我定义了一个函数 `Partial`,这个函数的功能入参是一个类型,返回值是新的类型,这个类型里的属性全部变成可选的。 + +伪代码: + +```js + +function Partial(Type) { + type ans = 空类型 + for(k in Type) { + 空类型[k] = makeOptional(Type, k) + } + return ans +} + +type PartialedPerson = Partial(Person) + +``` + +可惜的是上面代码不能运行,也不可能运行。不可能运行的原因有: + +- 这里使用函数 Partial 操作类型,可以看出上面的函数我是没有添加签名的,我是故意的。如果让你给这个函数添加签名你怎么加?没办法加! +- 这里使用 JS 的语法对类型进行操作,这是不恰当的。首先这种操作依赖了 JS 运行时,而 TS 是静态分析工具,不应该依赖 JS 运行时。其次如果要支持这种操作是否意味者 TS 对 JS 妥协,JS 出了新的语法(比如早几年出的 async await),TS 都要支持其对 TS 进行操作。 + +因此迫切需要一种不依赖 JS 行为,特别是运行时行为的方式,并且逻辑其实和上面类似的,且不会和现有语法体系冲突的语法。 我们看下 TS 团队是怎么做的: + +```js +// 可以看成是上面的函数定义,可以接受任意类型。由于是这里的 “Type” 形参,因此理论上你叫什么名字都是无所谓的,就好像函数定义的形参一样。 +type Partial = { do something } +// 可以看成是上面的函数调用,调用的时候传入了具体的类型 Person +type PartialedPerson = Partial +``` + +先不管功能,我们来看下这两种写法有多像: + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxpuyc9hjj30nf05awhq.jpg) + +(定义) + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxpvjwoktj30od04tgnz.jpg) + +(运行) + +再来看下上面泛型的功能。上面代码的意思是对 T 进行处理,是返回一个 T 的子集,具体来说就是将 T 的所有属性变成可选。这时 `PartialedPerson` 就等于 : + +```ts +interface Person { + name?: string; + sex?: Sex; + age?: number; +} +``` + +> 功能和上面新建一个新的 interface 一样,但是更优雅。 + +最后来看下泛型 Partial 的具体实现,可以看出其没有直接使用 JS 的语法,而是自己定义了一套语法,比如这里的 `keyof`,至此完全应证了我上面的观点。 + +```ts +type Partial = { [P in keyof T]?: T[P] }; +``` + +> 刚才说了“由于是形参,因此起什么名字无所谓” 。因此这里就起了 T 而不是 Type,更短了。这也算是一种约定俗称的规范,大家一般习惯叫 T, U 等表示泛型的形参。 + +我们来看下完整的泛型和函数有多像! + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfwakdx0mcj30z40cywha.jpg) + +(定义) + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfwam1jg34j30fx0df75m.jpg) + +(使用) + +- 从外表看只不过是 `function` 变成了 `type`,`()` 变成了 `<>`而已。 + +- 从语法规则上来看, 函数内部对标的是 ES 标准。而泛型对应的是 TS 实现的一套标准。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfwau8hq65j30yb0ad3zx.jpg) + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfwce290nzj310t0km410.jpg) + +简单来说,将类型看成值,然后对类型进行编程,这就是泛型的基本思想。泛型类似我们平时使用的函数,只不过其是作用在类型上,思想上和我们平时使用的函数并没有什么太多不同,泛型产生的具体类型也支持类型的操作。比如: + +```js +type ComponentType

= ComponentClass

| FunctionComponent

; +``` + +有了上面的知识,我们通过几个例子来巩固一下。 + +```js +function id(arg1: T, arg2: U): T { + return arg1; +} +``` + +上面定义了泛型 id,其入参分别是 T 和 U,和函数参数一样,使用逗号分隔。定义了形参就可以在函数体内使用形参了。如上我们在函数的参数列表和返回值中使用了形参 T 和 U。 + +返回值也可以是复杂类型: + +```js +function ids(arg1: T, arg2: U): [T, U] { + return [arg1, arg2]; +} +``` + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfz110rqdzj31sm0l278m.jpg) + +(泛型的形参) + +和上面类似, 只不过返回值变成了数组而已。 + +需要注意的是,思想上我们可以这样去理解。但是具体的实现过程会有一些细微差别,比如: + +```ts +type P = [number, string, boolean]; +type Q = Date; + +type R = [Q, ...P]; // A rest element type must be an array type. +``` + +再比如: + +```ts +type Lucifer = LeetCode; +type LeetCode = { + name: T; +}; + +const a: LeetCode; //ok +const a: Lucifer; // Type 'Lucifer' is not generic. +``` + +改成这样是 ok 的: + +```ts +type Lucifer = LeetCode; +``` + +## 泛型为什么使用尖括号 + +为什么泛型要用尖括号(<>),而不是别的? 我猜是因为它和 () 长得最像,且在现在的 JS 中不会有语法歧义。但是,它和 JSX 不兼容!比如: + +```tsx +function Form() { + // ... + + return ( + options={targets} value={target} onChange={setTarget} /> + ); +} +``` + +这是因为 TS 发明这个语法的时候,还没想过有 JSX 这种东西。后来 TS 团队在 TypeScript 2.9 版本修复了这个问题。也就是说现在你可以直接在 TS 中使用带有泛型参数的 JSX 啦(比如上面的代码)。 + +## 泛型的种类 + +实际上除了上面讲到的函数泛型,还有接口泛型和类泛型。不过语法和含义基本同函数泛型一样: + +```js +interface id { + id1: T; + id2: U; +} +``` + +(接口泛型) + +```js +class MyComponent extends React.Component { + ... +} +``` + +(类泛型) + +总结下就是: 泛型的写法就是在标志符后面添加尖括号(<>),然后在尖括号里写形参,并在 body(函数体, 接口体或类体) 里用这些形参做一些逻辑处理。 + +## 泛型的参数类型 - “泛型约束” + +正如文章开头那样,我们可以对函数的参数进行限定。 + +```js +function t(name: string) { + return `hello, ${name}`; +} +t("lucifer"); +``` + +如上代码对函数的形参进行了类型限定,使得函数仅可以接受 string 类型的值。那么泛型如何达到类似的效果呢? + +```js +type MyType = (T: constrain) => { do something }; +``` + +还是以 id 函数为例,我们给 id 函数增加功能,使其不仅可以返回参数,还会打印出参数。熟悉函数式编程的人可能知道了,这就是 trace 函数,用于调试程序。 + +```js +function trace(arg: T): T { + console.log(arg); + return arg; +} +``` + +假如我想打印出参数的 size 属性呢?如果完全不进行约束 TS 是会报错的: + +> 注意:不同 TS 版本可能提示信息不完全一致,我的版本是 3.9.5。下文的所有测试结果均是使用该版本,不再赘述。 + +```js +function trace(arg: T): T { + console.log(arg.size); // Error: Property 'size doesn't exist on type 'T' + return arg; +} +``` + +报错的原因在于 T 理论上是可以是任何类型的,不同于 any,你不管使用它的什么属性或者方法都会报错(除非这个属性和方法是所有集合共有的)。那么直观的想法是限定传给 trace 函数的**参数类型**应该有 size 类型,这样就不会报错了。如何去表达这个**类型约束**的点呢?实现这个需求的关键在于使用类型约束。 使用 extends 关键字可以做到这一点。简单来说就是你定义一个类型,然后让 T 实现这个接口即可。 + +```js +interface Sizeable { + size: number; +} +function trace(arg: T): T { + console.log(arg.size); + return arg; +} + +``` + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfwflqisraj312a0mijv8.jpg) + +这个时候 T 就不再是任意类型,而是被实现接口的 shape,当然你也可以继承多个接口。**类型约束是非常常见的操作,大家一定要掌握。** + +> 有的人可能说我直接将 Trace 的参数限定为 Sizeable 类型可以么?如果你这么做,会有类型丢失的风险,详情可以参考这篇文章[A use case for TypeScript Generics](https://juliangaramendy.dev/when-ts-generics/ "A use case for TypeScript Generics")。 + +## 常见的泛型 + +### 集合类 + +大家平时写 TS 一定见过类似 `Array` 这种写法吧? 这其实是集合类,也是一种泛型。 + +本质上数组就是一系列值的集合,这些值可以可以是任意类型,数组只是一个容器而已。然而平时开发的时候通常数组的项目类型都是相同的,如果不加约束的话会有很多问题。 比如我应该是一个字符串数组,然是却不小心用到了 number 的方法,这个时候类型系统应该帮我识别出这种**类型问题**。 + +由于数组理论可以存放任意类型,因此需要使用者动态决定你想存储的数据类型,并且这些类型只有在被调用的时候才能去确定。 `Array` 就是调用,经过这个调用会产生一个具体集合,这个集合只能存放 string 类型的值。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxqhlaya6j317y0dcgnl.jpg) + +不调用直接把 Array 是不被允许的: + +```js +const a: Array = ["1"]; +``` + +如上代码会被错:`Generic type 'Array' requires 1 type argument(s).ts` 。 有没有觉得和函数调用没传递参数报错很像?像就对了。 + +这个时候你再去看 Set, Promise,是不是很快就知道啥意思了?它们本质上都是包装类型,并且支持多种参数类型,因此可以用泛型来约束。 + +### React.FC + +大家如果开发过 React 的 TS 应用,一定知道 `React.FC` 这个类型。我们来看下它是如何[定义](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts "React.FC Type Definition")的: + +```js +type FC

= FunctionComponent

; + +interface FunctionComponent

{ + (props: PropsWithChildren

, context?: any): ReactElement | null; + propTypes?: WeakValidationMap

; + contextTypes?: ValidationMap; + defaultProps?: Partial

; + displayName?: string; +} +``` + +可以看出其大量使用了泛型。你如果不懂泛型怎么看得懂呢?不管它多复杂,我们从头一点点分析就行,记住我刚才讲的类比方法,将泛型类比到函数进行理解。· + +- 首先定义了一个泛型类型 FC,这个 FC 就是我们平时用的 React.FC。它是通过另外一个泛型 FunctionComponent 产生的。 + +> 因此,实际上第一行代码的作用就是起了一个别名 + +- FunctionComponent 实际上是就是一个接口泛型,它定义了五个属性,其中四个是可选的,并且是静态类属性。 +- displayName 比较简单,而 propTypes,contextTypes,defaultProps 又是通过其他泛型生成的类型。我们仍然可以采用我的这个分析方法继续分析。由于篇幅原因,这里就不一一分析,读者可以看完我的分析过程之后,自己尝试分析一波。 +- `(props: PropsWithChildren

, context?: any): ReactElement | null;` 的含义是 FunctionComponent 是一个函数,接受两个参数(props 和 context )返回 ReactElement 或者 null。ReactElement 大家应该比较熟悉了。`PropsWithChildren` 实际上就是往 props 中插入 children,源码也很简单,代码如下: + +```js +type PropsWithChildren

= P & { children?: ReactNode }; +``` + +这不就是我们上面讲的**集合操作**和 **可选属性**么?至此,React.FC 的全貌我们已经清楚了。读者可以试着分析别的源码检测下自己的学习效果,比如 `React.useState` 类型的签名。 + +## 类型推导与默认参数 + +类型推导和默认参数是 TS 两个重要功能,其依然可以作用到泛型上,我们来看下。 + +### 类型推导 + +我们一般常见的类型推导是这样的: + +```js +const a = "lucifer"; // 我们没有给 a 声明类型, a 被推导为 string +a.toFixed(); // Property 'toFixed' does not exist on type 'string'. +a.includes("1"); // ok +``` + +需要注意的是,类型推导是仅仅在初始化的时候进行推导,如下是无法正确推导的: + +```js +let a = "lucifer"; // 我们没有给 a 声明类型, a 被推导为string +a.toFixed(); // Property 'toFixed' does not exist on type 'string'. +a.includes("1"); // ok +a = 1; +a.toFixed(); // 依然报错, a 不会被推导 为 number +``` + +而泛型也支持类型推导,以上面的 id 函数为例: + +```ts +function id(arg: T): T { + return arg; +} +id("lucifer"); // 这是ok的,也是最完整的写法 +id("lucifer"); // 基于类型推导,我们可以这样简写 +``` + +这也就是为什么 useState 有如下两种写法的原因。 + +```ts +const [name, setName] = useState("lucifer"); +const [name, setName] = useState("lucifer"); +``` + +实际的类型推导要更加复杂和智能。相信随着时间的推进,TS 的类型推导会更加智能。 + +### 默认参数 + +和`类型推导`相同的点是,默认参数也可以减少代码量,让你少些代码。前提是你要懂,不然伴随你的永远是大大的问号。其实你完全可以将其类比到函数的默认参数来理解。 + +举个例子: + +```ts +type A = Array; +const aa: A = [1]; // type 'number' is not assignable to type 'string'. +const bb: A = ["1"]; // ok +const cc: A = [1]; // ok +``` + +上面的 A 类型默认是 string 类型的数组。你可以不指定,等价于 Array,当然你也可以显式指定数组类型。有一点需要注意:在 JS 中,函数也是值的一种,因此: + +```js +const fn = () => null; // ok +``` + +但是泛型这样是不行的,这是和函数不一样的地方(设计缺陷?Maybe): + +```js +type A = Array; // error: Generic type 'Array' requires 1 type argument(s). +``` + +其原因在与 Array 的定义是: + +```ts +interface Array { + ... +} +``` + +而如果 Array 的类型也支持默认参数的话,比如: + +```ts +interface Array { + ... +} +``` + +那么 `type A = Array;` 就是成立的,如果不指定的话,会默认为 string 类型。 + +## 什么时候用泛型 + +如果你认真看完本文,相信应该知道什么时候使用泛型了,我这里简单总结一下。 + +当你的函数,接口或者类: + +- 需要作用到很多类型的时候,比如我们介绍的 id 函数的泛型声明。 +- 需要被用到很多地方的时候,比如我们介绍的 Partial 泛型。 + +## 进阶 + +上面说了泛型和普通的函数有着很多相似的地方。普通的函数可以嵌套其他函数,甚至嵌套自己从而形成递归。泛型也是一样! + +### 泛型支持函数嵌套 + +比如: + +```ts +type CutTail = Reverse>>; +``` + +如上代码中, Reverse 是将参数列表反转,CutHead 是将数组第一项切掉。因此 CutTail 的意思就是将传递进来的参数列表反转,切掉第一个参数,然后反转回来。换句话说就是切掉参数列表的最后一项。 比如,一个函数是 function fn (a: string, b: number, c: boolean):boolean {},那么经过操作`type cutTailFn = CutTail`,可以返回`(a: string, b:number) => boolean`。 具体实现可以参考[Typescript 复杂泛型实践:如何切掉函数参数表的最后一个参数?](https://zhuanlan.zhihu.com/p/147248333 "Typescript 复杂泛型实践:如何切掉函数参数表的最后一个参数?")。 在这里,你知道泛型支持嵌套就够了。 + +### 泛型支持递归 + +泛型甚至可以嵌套自己从而形成递归,比如我们最熟悉的单链表的定义就是递归的。 + +```ts +type ListNode = { + data: T; + next: ListNode | null; +}; +``` + +(单链表) + +再比如 **HTMLElement** 的定义。 + +```ts +declare var HTMLElement: { + prototype: HTMLElement; + new(): HTMLElement; +};。 +``` + +([HTMLElement](https://github.com/microsoft/TypeScript/blob/master/lib/lib.dom.d.ts "HTMLElement Type Definition")) + +上面是**递归声明**,我们再来看一个更复杂一点的递归形式 - **递归调用**,这个递归调用的功能是:**递归地将类型中所有的属性都变成可选**。类似于深拷贝那样,只不过这不是拷贝操作,而是变成可选,并且是作用在类型,而不是值。 + +```ts +type DeepPartial = T extends Function + ? T + : T extends object + ? { [P in keyof T]?: DeepPartial } + : T; + +type PartialedWindow = DeepPartial; // 现在window 上所有属性都变成了可选啦 +``` + +## TS 泛型工具及实现 + +虽然泛型支持函数的嵌套,甚至递归,但是其语法能力肯定和 JS 没法比, 想要实现一个泛型功能真的不是一件容易的事情。这里提供几个例子,看完这几个例子,相信你至少可以达到比葫芦画瓢的水平。这样多看多练,慢慢水平就上来了。 + +截止目前(2020-06-21),TS 提供了 [16 种工具类型](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialt "TS 官方的16 种工具类型")。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfzuu9kjtnj30g80nqmze.jpg) + +(官方提供的工具类型) + +除了官方的工具类型,还有一些社区的工具类型,比如[type-fest](https://github.com/sindresorhus/type-fest "type-fest"),你可以直接用或者去看看源码看看高手是怎么玩类型的。 + +我挑选几个工具类,给大家讲一下**实现原理**。 + +### Partial + +功能是将类型的属性**变成可选**。注意这是浅 Partial,DeepPartial 上面我讲过了,只要配合递归调用使用即可。 + +```ts +type Partial = { [P in keyof T]?: T[P] }; +``` + +### Required + +功能和`Partial` 相反,是将类型的属性**变成必填**, 这里的 `-`指的是去除。 `-?` 意思就是去除可选,也就是必填啦。 + +```ts +type Required = { [P in keyof T]-?: T[P] }; +``` + +### Mutable + +功能是将类型的属性**变成可修改**,这里的 `-`指的是去除。 `-readonly` 意思就是去除只读,也就是可修改啦。 + +```ts +type Mutable = { + -readonly [P in keyof T]: T[P]; +}; +``` + +### Readonly + +功能和`Mutable` 相反,功能是将类型的属性**变成只读**, 在属性前面增加 `readonly` 意思会将其变成只读。 + +```ts +type Readonly = { readonly [P in keyof T]: T[P] }; +``` + +### ReturnType + +功能是用来得到一个函数的返回值类型。 + +```ts +type ReturnType any> = T extends ( + ...args: any[] +) => infer R + ? R + : any; +``` + +下面的示例用 ReturnType 获取到 Func 的返回值类型为 string,所以,foo 也就只能被赋值为字符串了。 + +```ts +type Func = (value: number) => string; + +const foo: ReturnType = "1"; +``` + +更多参考[TS - es5.d.ts](https://github.com/microsoft/TypeScript/blob/master/src/lib/es5.d.ts#L1431 "TS - es5.d.ts") 这些泛型可以极大减少大家的冗余代码,大家可以在自己的项目中自定义一些工具类泛型。 + +## Bonus - 接口智能提示 + +最后介绍一个实用的小技巧。如下是一个接口的类型定义: + +```ts +interface Seal { + name: string; + url: string; +} +interface API { + "/user": { name: string; age: number; phone: string }; + "/seals": { seal: Seal[] }; +} +const api = (url: URL): Promise => { + return fetch(url).then((res) => res.json()); +}; +``` + +我们通过泛型以及泛型约束,实现了智能提示的功能。使用效果: + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfzwmfvajej30tu04cglu.jpg) + +(接口名智能提示) + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfzwn2fovdj313u03egm2.jpg) + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfzxwbhc6yj314q05oaaq.jpg) + +(接口返回智能提示) + +原理很简单,当你仅输入 api 的时候,其会将 API interface 下的所有 key 提示给你,当你输入某一个 key 的时候,其会根据 key 命中 interface 定义的类型,然后给予类型提示。 + +## 总结 + +学习 Typescript 并不是一件简单的事情,尤其是没有其他语言背景的情况。而 TS 中最为困难的内容之一恐怕就是泛型了。 + +泛型和我们平时使用的函数是很像的,如果将两者进行横向对比,会很容易理解,很多函数的都关系可以迁移到泛型,比如函数嵌套,递归,默认参数等等。泛型是对类型进行编程,参数是类型,返回值是一个新的类型。我们甚至可以对泛型的参数进行约束,就类似于函数的类型约束。 + +最后通过几个高级的泛型用法以及若干使用的泛型工具类帮助大家理解和消化上面的知识。要知道真正的 TS 高手都是玩类型的,高手才不会满足于类型的交叉并操作。 泛型用的好确实可以极大减少代码量,提高代码维护性。如果用的太深入,也可能会团队成员面面相觑,一脸茫然。因此抽象层次一定要合理,不仅仅是泛型,整个软件工程都是如此。 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) diff --git a/docs/topics/ts/ts-internal.md b/docs/topics/ts/ts-internal.md new file mode 100644 index 0000000..3d7aca9 --- /dev/null +++ b/docs/topics/ts/ts-internal.md @@ -0,0 +1,140 @@ +--- +title: 上帝视角看 TypeScript +tags: [前端, TypeScript, 泛型] +categories: [前端, TypeScript, 泛型] +--- + +TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: + +- 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在**逻辑上**比较零散。 +- 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 +- 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 + +因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 而本篇文章则是这个系列的开篇。 + +系列安排: + +- 上帝视角看 TypeScript(就是本文) +- TypeScript 类型系统 +- 什么是 types?什么是 @types? +- 类型推导, 类型断言与类型保护 +- [你不知道的 TypeScript 泛型(万字长文,建议收藏)](https://lucifer.ren/blog/2020/06/16/ts-generics/)(已发布) +- TypeScript 练习题 +- TypeScript 配置文件该怎么写? +- TypeScript 是如何与 React,Vue,Webpack 集成的? + +> 目录将来可能会有所调整。 + +注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 + +- [深入理解 TypeScript](https://jkchao.github.io/typescript-book-chinese/) +- [官方文档](https://www.typescriptlang.org/docs/home) + +结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 + +接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 + + + +## 从输入输出上来看 + +如果我们把 Typescript 编译器看成一个黑盒的话。其**输入则是使用 TypeScript 语法书写的文本或者文本集合**。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gh5l5pqyw1j304s04wwea.jpg) + +(文本) + +如果几个文本有引用关系,比如 a.ts 依赖 foo.ts 和 bar.ts,其就是一个文本集合。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gh5l7apwnnj30ho09f74h.jpg) + +(文本集合) + +**输出是编译之后的 JS 文件 和 .d.ts 的声明文件**。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gh5ld0kfitj30ow0csdjs.jpg) + +其中 JS 是将来需要运行的文件,而 .d.ts 声明文件则是 ts 文件中的类型声明,**这个类型声明就是你在 ts 文件中声明的类型和 TypeScript 类型推导系统推导的类型**。当然你也可以自己写 .d.ts 声明文件。 + +## 从功能上来看 + +从宏观的视角来看,TypeScript 的功能就是: + +- 提供了丰富的类型系统。 + +最简单的就是 变量名:类型 = 值 + +```ts +const a: Number = 1; +``` + +除了这些基本类型,还提供了函数类型,复合类型等。 + +- 提供了类型操作 API。TypeScript 不但提供内置类型,用户也可以利用集合操作和泛型对类型操作从而生成新的类型。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gh5lzqpqirj30d104ogml.jpg) + +- 对每一种类型的属性和方法都进行了定义。 + +比如 String 类型有 toString 方法,但是没有 toFixed 方法,这就是 lib.d.ts 定义的。这样我在 String 类型的变量上使用 toFixed 方法就会报错,达到了“类型检查”的作用。 + +> 小提示:lib.d.ts 的内容主要是一些变量声明(如:window、document、math)和一些类似的接口声明(如:Window、Document、Math)。 你可以通过 --noLib 来关闭这一功能 + +- 提供了模块系统(module,namespace)。 +- 提供了更加方面的 API,比如 class(这在 ES6 class 出来之前尤其好用),装饰器等。 +- 。。。 + +## TypeScript 编译器是如何工作的? + +上面已经讨论了 TypeScript 编译器的输入和输出。那黑盒内部是怎么工作呢?这里我简单介绍一下: + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gh5nm8tmokj30dl02zq2s.jpg) + +- TypeScript 文本首先会被解析为 **token 流**。这个过程比较简单,就是单纯地按照分隔符去分割文本即可。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gh5npflqbbj30eh0490sw.jpg) + +- 接着 token 流会被转换为 AST,也就是**抽象语法树**。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gh5nqa41bpj309106lt8z.jpg) + +- binder 则根据 AST 信息生成 **Symbol**(TypeScript 中的一个数据结构)。拿上面的图来说,就是 number 节点。 +- 当我们需要类型检查的时候, checker 会根据**前面生成的 AST 和 symbols 生成类型检查结果**。 +- 当我们需要生成 JS 文件的时候,emitter 同样会根据**前面生成的 AST 和 symbols 生成 JS 文件**。 + +完整图: + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gh5nfcui6sj30xz0gendl.jpg) + +## 总结 + +总的来说,TypeScript 就是一门语言,和 Java,Python,C++ 等类似。只不过这门语言主要目标就是为了弥补 JavaScript 弱类型带来的问题的。因此设计语言的出发点就是: + +- 静态类型系统 +- 可以编译成 JavaScript + +因此 TypeScript 是一门最终编译为 JavaScript 的语言(当然还有类型文件)。既然是一门语言,就涉及词法分析,语法分析等流程。由于相对 JavaScript 增加了很多功能, 其中最主要的就是类型系统。因此 TypeScript 的分析工作要比 JavaScript 更加复杂, 集中体现在 binder 和 checker 部分。 + +由于提供了静态类型, 因此就需要提供一些内置类型给我们用,比如 number,string,Array 等。但是这并不能满足我们的所有需求,我们需要自定义类型,因此有了 type,有了 interface 等。后来我们又发现自定义的类型重复代码太多, 要是类型也可以通过编程生成新的类型就好了,于是有了集合运算和泛型。 + +代码都放到一起不方便维护,要是可以放到不同文件,需要用的时候组装起来就好了,于是有了模块化。我用了别人的用 TypeScript 开发的库,如果也能有类型校验就好了,于是有了 types。 + +。。。 + +其实这些都是有因果关系的,如果你可以牢牢地掌握这些因果关系,那么学起来还不是易如反掌? + +## 相关阅读 + +- [TypeScript 编译原理](https://jkchao.github.io/typescript-book-chinese/compiler/overview.html) +- [Bring your own TypeScript with more internal definitions](https://github.com/basarat/byots) +- [Compiler Internals](https://github.com/microsoft/TypeScript/wiki/Compiler-Internals) +- [TypeScript 编译器是用 TypeScript 写的,那是先有编译器还是 TS?](https://github.com/azl397985856/fe-interview/issues/135) + +## 点关注,不迷路 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) + +公众号【 [力扣加加](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)】 +知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 diff --git a/docs/topics/ts/ts-type-system.md b/docs/topics/ts/ts-type-system.md new file mode 100644 index 0000000..d0f29c6 --- /dev/null +++ b/docs/topics/ts/ts-type-system.md @@ -0,0 +1,318 @@ +--- +title: TypeScript 类型系统 +tags: [前端, TypeScript] +date: 2020-08-15 +categories: + - [前端, TypeScript] +--- + +TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: + +- 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在**逻辑上**比较零散。 +- 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 +- 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 + +因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 + +系列安排: + +- [上帝视角看 TypeScript](https://lucifer.ren/blog/2020/08/04/ts-internal/ "上帝视角看 TypeScrip")(已发布) +- TypeScript 类型系统(就是本文) +- types 和 @types 是什么? +- [你不知道的 TypeScript 泛型(万字长文,建议收藏)](https://lucifer.ren/blog/2020/06/16/ts-generics/ "你不知道的 TypeScript 泛型(万字长文,建议收藏)")(已发布) +- TypeScript 配置文件该怎么写? +- TypeScript 是如何与 React,Vue,Webpack 集成的? +- TypeScript 练习题 + +> 目录将来可能会有所调整。 + +注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 + +- [深入理解 TypeScript](https://jkchao.github.io/typescript-book-chinese/) +- [官方文档](https://www.typescriptlang.org/docs/home) + +结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 + +接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 + + + +## 前言 + +上一节的[上帝视角看 TypeScript](https://lucifer.ren/blog/2020/08/04/ts-internal/),我们从宏观的角度来对 Typescript 进行了一个展望。之所以把那个放到开头讲是让大家有一个大体的认识,不想让大家一叶障目。当你对整个宏观层面有了一定的了解,那么对 Typescript 的理解就不会错太多。相反,一开始就是具体的概念和 API,则很可能会让你丧失都整体的基本判断。 + +实际上, Typescript 一直在不断更新迭代。一方面是因为当初许下的诺言”Typescript 是 JavaScript 的超集“(JavaScript 的特性你要同步支持,同时也要处理各种新语法带来的不兼容情况)。不单是 ECMA,社区的其他发展可能也会让 Typescript 很难受。 比如 JSX 的广泛使用就给 Typescript 泛型的使用带来了影响。 + +TypeScript 一直处于高速的迭代。除了修复日常的 bug 之外,TypeScript 也在不断发布新的功能,比如最新 [4.0.0 beta 版本的**标签元祖**](https://devblogs.microsoft.com/typescript/announcing-typescript-4-0-rc/#labeled-tuple-elements "4.0.0 beta 版本的**标签元祖**") 的功能就对智能提示这块很有用。Typescript 在社区发展方面也做的格外好,以至于它的竞争对手 Flow 被 Typescript 完美击败,这在很大程度上就是因为 Typescript 没有烂尾。如今微软在开源方向的发力是越来越显著了,我很期待微软接下来的表现,让我们拭目以待。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghsqcjsrx9j30xc0b4js3.jpg) + +## 变量类型和值类型 + +有的同学可能有疑问, JavaScript 不是也有类型么? 它和 Typescript 的类型是一回事么?JavaScript 不是动态语言么,那么经过 Typescript 的限定会不会丧失动态语言的动态性呢?我们继续往下看。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghsqeaxx8jj30pq09qdga.jpg) + +- JavaScript 中的类型其实是值的类型。实际上不仅仅是 JavaScript,任何动态类型语言都是如此,这也是动态类型语言的本质。 + +- Typescript 中的类型其实是变量的类型。实际上不仅仅是 Typescript,任何静态类型语言都是如此,这也是静态类型语言的本质。 + +记住这两句话,我们接下来解释一下这两句话。 + +对于 JavaScript 来说,一个变量可以是任意类型。 + +```js +var a = 1; +a = "lucifer"; +a = {}; +a = []; +``` + +上面的值是有类型的。比如 1 是 number 类型,"lucifer" 是字符串类型, {} 是对象类型, [] 是数组类型。而变量 a 是没有固定类型的。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghsqh8ere4j30qc0iygmv.jpg) + +对于 Typescript 来说, 一个变量只能接受和它类型兼容的类型的值。说起来比较拗口, 看个例子就明白了。 + +```ts +var a: number = 1; +a = "lucifer"; // error +var b: any = 1; +b = "lucifer"; // ok +b = {}; // ok +b = []; // ok +``` + +我们不能将 string 类型的值赋值给变量 a, 因为 string 和 number 类型不兼容。而我们可以将 string,Object,Array 类型的值赋值给 b,因此 它们和 any 类型兼容。简单来说就是,一旦一个变量被标注了某种类型,那么其就只能接受这个类型以及它的子类型。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghsqgexx68j30q80j6ta0.jpg) + +## 类型空间和值空间 + +类型和值居住在不同的空间,一个在阳间一个在阴间。他们之间互相不能访问,甚至不知道彼此的存在。类型不能当做值来用,反之亦然。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghsqpfc7uej307b05baa0.jpg) + +### 类型空间 + +如下代码会报类型找不到的错: + +```ts +const aa: User = { name: "lucifer", age: 17 }; +``` + +这个比较好理解,我们只需要使用 interface 声明一下 User 就行。 + +```ts +interface User { + name: string; + age: number; +} + +const aa: User = { name: "lucifer", age: 17 }; +``` + +也就是说使用 interface 可以在类型空间声明一个类型,这个是 Typescript 的类型检查的基础之一。 + +实际上类型空间内部也会有子空间。我们可以用 namespace(老)和 module(新) 来创建新的子空间。子空间之间不能直接接触,需要依赖导入导出来交互。 + +### 值空间 + +比如,我用 Typescript 写出如下的代码: + +```ts +const a = window.lucifer(); +``` + +Typescript 会报告一个类似`Property 'lucifer' does not exist on type 'Window & typeof globalThis'.` 的错误。 + +实际上,这种错误并不是类型错误,而是找不到成员变量的错误。我们可以这样解决: + +```ts +declare var lucifer: () => any; +``` + +也就是说使用 declare 可以在值空间声明一个变量。这个是 Typescript 的变量检查的基础,不是本文要讲的主要内容,大家知道就行。 + +明白了 JavaScript 和 TypeScript 类型的区别和联系之后,我们就可以来进入我们本文的主题了:**类型系统**。 + +## 类型系统是 TypeScript 最主要的功能 + +TypeScript 官方描述中有一句:**TypeScript adds optional types to JavaScript that support tools for large-scale JavaScript applications**。实际上这也正是 Typescript 的主要功能,即给 JavaScript 添加静态类型检查。要想实现静态类型检查,首先就要有类型系统。总之,我们使用 Typescript 的主要目的仍然是要它的静态类型检查,帮助我们提供代码的扩展性和可维护性。因此 Typescript 需要维护一套完整的类型系统。 + +**类型系统包括 1. 类型 和 2.对类型的使用和操作**,我们先来看类型。 + +### 类型 + +TypeScript 支持 JavaScript 中所有的类型,并且还支持一些 JavaScript 中没有的类型(毕竟是超集嘛)。没有的类型可以直接提供,也可以提供自定义能力让用户来自己创造。 那为什么要增加 JavaScript 中没有的类型呢?我举个例子,比如如下给一个变量声明类型为 Object,Array 的代码。 + +```ts +const a: Object = {}; +const b: Array = []; +``` + +其中: + +- 第一行代码 Typescript 允许,但是太宽泛了,我们很难得到有用的信息,推荐的做法是使用 interface 来描述,这个后面会讲到。 + +- 第二行 Typescript 则会直接报错,原因的本质也是太宽泛,我们需要使用泛型来进一步约束。 + +### 对类型的使用和操作 + +上面说了**类型和值居住在不同的空间,一个在阳间一个在阴间。他们之间互相不能访问,甚至不知道彼此的存在。** + +使用 declare 和 interface or type 就是分别在两个空间编程。比如 Typescript 的泛型就是在类型空间编程,叫做类型编程。除了泛型,还有集合运算,一些操作符比如 keyof 等。值的编程在 Typescript 中更多的体现是在类似 lib.d.ts 这样的库。当然 lib.d.ts 也会在类型空间定义各种内置类型。我们没有必要去死扣这个,只需要了解即可。 + +lib.d.ts 的内容主要是一些变量声明(如:window、document、math)和一些类似的接口声明(如:Window、Document、Math)。寻找代码类型(如:Math.floor)的最简单方式是使用 IDE 的 F12(跳转到定义)。 + +## 类型是如何做到静态类型检查的? + +TypeScript 要想解决 JavaScript 动态语言类型太宽松的问题,就需要: + +1. 提供给**变量**设定类型的能力 + +> 注意是变量,不是值。 + +2. 提供常用类型(不必须,但是没有用户体验会极差)并可以扩展出自定义类型(必须)。 + +3. 根据第一步给变量设定的类型进行类型检查,即不允许类型不兼容的赋值, 不允许使用值空间和类型空间不存在的变量和类型等。 + +第一个点是通过类型注解的语法来完成。即类似这样: + +```ts +const a: number = 1; +``` + +> Typescript 的类型注解是这样, Java 的类型注解是另一个样子,Java 类似 int a = 1。 这个只是语法差异而已,作用是一样的。 + +第二个问题, Typescript 提供了诸如 lib.d.ts 等类型库文件。随着 ES 的不断更新, JavaScript 类型和全局变量会逐渐变多。Typescript 也是采用这种 lib 的方式来解决的。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghmyjgyd7qj307o0lxgmq.jpg) + +(TypeScript 提供的部分 lib) + +第三个问题,Typescript 主要是通过 interface,type,函数类型等打通**类型空间**,通过 declare 等打通**值空间**,并结合 binder 来进行类型诊断。关于 checker ,binder 是如何运作的,可以参考我第一篇的介绍。 + +接下来,我们介绍类型系统的功能,即它能为我们带来什么。如果上面的内容你已经懂了,那么接下来的内容会让你感到”你也不过如此嘛“。 + +## 类型系统的主要功能 + +1. 定义类型以及其上的属性和方法。 + +比如定义 String 类型, 以及其原型上的方法和属性。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghsqk1u3x8j31d60u0q9u.jpg) + +length, includes 以及 toString 是 String 的**成员变量**, 生活在值空间, 值空间虽然不能直接和类型空间接触,但是类型空间可以作用在值空间,从而给其添加类型(如上图黄色部分)。 + +2. 提供自定义类型的能力 + +```ts +interface User { + name: string; + age: number; + say(name: string): string; +} +``` + +这个是我自定义的类型 User,这是 Typescript 必须提供的能力。 + +3. 类型兼容体系。 + +这个主要是用来判断类型是否正确的,上面我已经提过了,这里就不赘述了。 + +4. 类型推导 + +有时候你不需要显式说明类型(类型注解),Typescript 也能知道他的类型,这就是类型推导结果。 + +```ts +const a = 1; +``` + +如上代码,编译器会自动推导出 a 的类型 为 number。还可以有连锁推导,泛型的入参(泛型的入参是类型)推导等。类型推导还有一个特别有用的地方,就是用到类型收敛。 + +接下来我们详细了解下类型推导和类型收敛。 + +## 类型推导和类型收敛 + +```ts +let a = 1; +``` + +如上代码。 Typescript 会推导出 a 的类型为 number。 + +如果只会你这么写就会报错: + +```ts +a = "1"; +``` + +因此 string 类型的值不能赋值给 number 类型的变量。我们可以使用 Typescript 内置的 typeof 关键字来证明一下。 + +```ts +let a = 1; +type A = typeof a; +``` + +此时 A 的类型就是 number,证明了变量 a 的类型确实被隐式推导成了 number 类型。 + +有意思的是如果 a 使用 const 声明,那么 a 不会被推导为 number,而是推导为类型 1。即**值只能为 1 的类型**,这就是类型收敛。 + +```ts +const a = 1; +type A = typeof a; +``` + +> 通过 const ,我们将 number 类型收缩到了 **值只能为 1 的类型**。 + +实际情况的类型推导和类型收敛要远比这个复杂, 但是做的事情都是一致的。 + +比如这个: + +```ts +function test(a: number, b: number) { + return a + b; +} +type A = ReturnType; +``` + +A 就是 number 类型。 也就是 Typescript 知道两个 number 相加结果也是一个 number。因此即使你不显示地注明返回值是 number, Typescript 也能猜到。**这也是为什么 JavaScript 项目不接入 Typescript 也可以获得类型提示的原因之一**。 + +除了 const 可以收缩类型, typeof, instanceof 都也可以。 原因很简单,就是**Typescript 在这个时候可以 100% 确定你的类型了**。 我来解释一下: + +比如上面的 const ,由于你是用 const 声明的,因此 100% 不会变,一定永远是 1,因此类型可以收缩为 1。 再比如: + +```ts +let a: number | string = 1; +a = "1"; +if (typeof a === "string") { + a.includes; +} +``` + +if 语句内 a 100% 是 string ,不能是 number。因此 if 语句内类型会被收缩为 string。instanceof 也是类似,原理一模一样。大家只要记住**Typescript 如果可以 100% 确定你的类型,并且这个类型要比你定义的或者 Typescript 自动推导的范围更小,那么就会发生类型收缩**就行了。 + +## 总结 + +本文主要讲了 Typescript 的类型系统。 Typescript 和 JavaScript 的类型是很不一样的。从表面上来看, TypeScript 的类型是 JavaScript 类型的超集。但是从更深层次上来说,两者的本质是不一样的,一个是值的类型,一个是变量的类型。 + +Typescript 空间分为值空间和类型空间。两个空间不互通,因此值不能当成类型,类型不能当成值,并且值和类型不能做运算等。不过 TypeScript 可以将两者结合起来用,这个能力只有 TypeScript 有, 作为 TypeScript 的开发者的你没有这个能力,这个我在第一节也简单介绍了。 + +TypeScript 既会对变量存在与否进行检查,也会对变量类型进行兼容检查。因此 TypeScript 就需要定义一系列的类型,以及类型之间的兼容关系。默认情况,TypeScript 是没有任何类型和变量的,因此你使用 String 等都会报错。TypeScript 使用库文件来解决这个问题,最经典的就是 lib.d.ts。 + +TypeScript 已经做到了足够智能了,以至于你不需要写类型,它也能猜出来,这就是类型推导和类型收缩。当然 TypeScript 也有一些功能,我们觉得应该有,并且也是可以做到的功能空缺。但是我相信随着 TypeScript 的逐步迭代(截止本文发布,TypeScript 刚刚发布了 4.0.0 的 beta 版本),一定会越来越完善,用着越来越舒服的。 + +我们每个项目的需要是不一样的, 简单的基本类型肯定无法满足多样的项目需求,因此我们必须支持自定义类型,比如 interface, type 以及复杂一点的泛型。当然泛型很大程度上是为了减少样板代码而生的,和 interface , type 这种刚需不太一样。 + +有了各种各样的类型以及类型上的成员变量,以及成员变量的类型,再就加上类型的兼容关系,我们就可以做类型检查了,这就是 TypeScript 类型检查的基础。TypeScript 内部需要维护这样的一个关系,并对变量进行类型绑定,从而给开发者提供**类型分析**服务。 + +## 关注我 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) + +公众号【 [力扣加加](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)】 +知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 + +点关注,不迷路! diff --git a/docs/topics/ts/ts-type.md b/docs/topics/ts/ts-type.md new file mode 100644 index 0000000..a50db74 --- /dev/null +++ b/docs/topics/ts/ts-type.md @@ -0,0 +1,140 @@ +--- +title: types 和 @types 是什么? +tags: [前端, TypeScript] +date: 2020-08-21 +categories: + - [前端, TypeScript] +--- + +TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: + +- 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在**_逻辑上_**比较零散。 +- 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 +- 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 + +因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 + +系列安排: + +- [上帝视角看 TypeScript(已发布)](https://lucifer.ren/blog/2020/08/04/ts-internal/) +- [TypeScript 类型系统(已发布)](https://lucifer.ren/blog/2020/08/15/ts-type-system/) +- types 和 @types 是什么?(就是本文) +- [你不知道的 TypeScript 泛型(万字长文,建议收藏)](https://lucifer.ren/blog/2020/06/16/ts-generics/)(已发布) +- TypeScript 配置文件该怎么写? +- TypeScript 是如何与 React,Vue,Webpack 集成的? +- TypeScript 练习题 + +> 目录将来可能会有所调整。 + +注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 + +- [深入理解 TypeScript](https://jkchao.github.io/typescript-book-chinese/) +- [官方文档](https://www.typescriptlang.org/docs/home) + +结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 + +接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 + + + +## 前言 + +- 作者:feiker & Lucifer + +TypeScript 中有几个概念和名字很像,会让初学者傻傻分不清楚。比如配置文件中的 **_types 和 typeRoots_**,并且还有一个 @types。接触过 TypeScript 的人一定接触过它们, 这几个有什么区别和联系呢?今天就带你来重新认识下它们。 + +## 一个例子 + +这里我通过一个例子来说明一下什么是 @types,这样大家理解起来更深刻一点。 + +当我们用 npm 等包管理工具安装第三方包的时候,有些包并不是 TypeScript 编写的,自然也不会导出 TypeScript 声明文件。这种情况下,如果我们在 TypeScript 项目中引入了这种包,则会编译报错(没有设置 allowJS)。举个例子,当我们通过`npm install jquery --save` 安装 jquery 包并引用的时候,TypeScript 会报错。 + +> allowJS 是 TypeScript 1.8 引进的一个编译项。 + +报错内容如下: + +> Could not find a declaration file for module ‘jquery’. Try `npm install @types/jquery` if it exists or add a new declaration (.d.ts) file containing `declare module 'jquery';` + +这里的意思是 TypeScript 没有找到 jquery 这个包的定义,你可以通过`npm install @types/jquery`安装相关声明,或者自己定义一份.d.ts 文件,并将 jquery 声明为 module。 + +全世界不是 TypeScript 编写的包多了去了。即使你的包是 TypeScript 编写的,如果你没有导出声明文件,也是没用的。(TypeScript 默认不会导出声明文件,只会编译输出 JavaScript 文件)。因此 TypeScript 必须对这种情况提供解决方案,而上面的两种方案(安装 @types 和 自己 declare module)就是 TypeScript 官方提出的, 你可以选择适合你的方案。我的推荐是尽量使用 @types 下的声明,实在没有,再使用第二种方法。 + +值得一提的是,并不是所有的包都可以通过这种方式解决的, 能解决的是 DefinitelyTyped 组织已经写好定义的包, 好消息是比较流行的包基本都有。 如果你想查一个包是否在 @type 下,可以访问 https://microsoft.github.io/TypeSearch/ + +那么 TypeScript 是怎么找定义的,什么情况会找不到定义而报类似上面举的例子的错误,这里简单介绍下原理。 + +## 包类型定义的查找 + +就好像 node 的包查找是先在当前文件夹找 node_modules,在它下找递归找,如果找不到则往上层目录继续找,直到顶部一样, TypeScript 类型查找也是类似的方式。 + +具体来说就是: + +- TypeScript 编译器先在当前编译上下文找 jquery 的定义。 +- 如果找不到,则会去 node_modules 中的@types (默认情况,目录可以修改,后面会提到)目录下去寻找对应包名的模块声明文件。 + +> `@types/*`模块声明文件由社区维护,通过发布到@types 空间下。 [GitHub - DefinitelyTyped/DefinitelyTyped: The repository for high quality TypeScript type definitions.](https://github.com/DefinitelyTyped/DefinitelyTyped) + +## 变量类型定义的查找 + +和包查找类似,默认情况下变量类型定义的查找也会去 @types 下去寻找。只不过并不是直接去 @types 找,而是有一定的优先级, 这个过程类似原型链或者作用域链。 + +比如如下代码: + +```ts +const user: User = { name: "lucifer" }; +``` + +- Typescript 则会先在本模块查找 User 的定义。 +- 如果找到,则直接返回。 如果找不到, 则会到全局作用域找,而这个全局默认就是指的就是 @types 下的所有类型定义。(注意目录页是可以配的) + +> 也就是说 @types 下的定义都是全局的。当然你可以导入 @types 下导出的定义,使得它们的作用域变成你的模块内部。 + +## typeRoots 与 types + +前面说了 TypeScript 会默认引入`node_modules`下的所有`@types`声明,但是开发者也可以通过修改`tsconfig.json`的配置来修改默认的行为. + +tsconfig.json 中有两个配置和类型引入有关。 + +1. `typeRoots`: 用来指定默认的类型声明文件查找路径,默认为`node_modules/@types`, 指定`typeRoots`后,TypeScript 编译器会从指定的路径去引入声明文件,而不是`node_modules/@types`, 比如以下配置会从`typings`路径下去搜索声明 + +```json +{ + "compilerOptions": { + "typeRoots": ["./typings"] + } +} +``` + +2. `types`: TypeScript 编译器会默认引入`typeRoot`下所有的声明文件,但是有时候我们并**_不希望全局引入所有定义_**,而是仅引入部分模块。这种情景下可以通过`types`指定模块名只引入我们想要的模块,比如以下只会引入 jquery 的声明文件 + +```json +{ + "compilerOptions": { + "types": ["jquery"] + } +} +``` + +## 总结 + +1. typeRoots 是 tsconfig 中 compilerOptions 的一个配置项,typeRoots 下面的包会被 ts 编译器自动包含进来,typeRoots 默认指向 node_modules/@types。 +2. @types 是 npm 的 scope 命名空间,和@babel 类似,@types 下的所有包会默认被引入,你可以通过修改 compilerOptions 来修改默认策略。 +3. types 和 typeRoots 一样也是 compilerOptions 的配置,指定 types 后,typeRoots 下只有被指定的包才会被引入。 + +## 参考 + +- [GitHub - DefinitelyTyped/DefinitelyTyped: The repository for high quality TypeScript type definitions.](https://github.com/DefinitelyTyped/DefinitelyTyped) +- [@types | 深入理解 TypeScript](https://jkchao.github.io/typescript-book-chinese/typings/types.html) +- [tsconfig.json · TypeScript 中文网 · TypeScript——JavaScript 的超集](https://www.tslang.cn/docs/handbook/tsconfig-json.html) +- [理解 Typescript 配置文件 - 个人文章 - SegmentFault 思否](https://segmentfault.com/a/1190000013514680) + +## 关注我 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) + +公众号【 [力扣加加](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)】 +知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 + +点关注,不迷路! diff --git a/pdf/readme.pdf b/pdf/readme.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c22ae0fa56c10f5077e027084fddba1628784367 GIT binary patch literal 13991 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 bW63?XIK#kBus{6+@*q2g%fuwBCinjUTya98 literal 0 HcmV?d00001 From 441c4b16138a56b96c6439c4235a29d41203f6b2 Mon Sep 17 00:00:00 2001 From: lucifer Date: Sat, 23 Jan 2021 12:12:36 +0800 Subject: [PATCH 06/13] =?UTF-8?q?feat:=20=E9=98=BF=E9=87=8Ccbu=E9=9D=A2?= =?UTF-8?q?=E8=AF=95=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 5 +- docs/topics/algorthimn/transformer.md | 127 ++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 docs/topics/algorthimn/transformer.md diff --git a/docs/README.md b/docs/README.md index 112829c..68975fe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -191,7 +191,7 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, - 原型链能够实现所谓的继承的本质原因是什么? - 箭头函数是用来解决什么问题的? - 什么是高阶函数?用处和用法? -- 什么是异步编程,为什么说它对Web开发很重要? +- 什么是异步编程,为什么说它对 Web 开发很重要? ### 编程题 ✍️ @@ -262,6 +262,7 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, - [已知数据格式,实现一个函数 fn 找出链条中所有的父级 id](./topics/algorthimn/findParents.md) - [获取页面所有的 tagname](./topics/algorthimn/getAllHTMLTags.md) - [实现 XPath](./topics/algorthimn/xpath.md) +- [数据结构转换](./topics/algorthimn/transformer.md) 🆕 ### 浏览器 @@ -538,7 +539,7 @@ Redux 中核心就是一个单一的 state。state 通过闭包的形式存放 如果上面的专题你都看过了,那么来回答几个问题看你是否真的掌握了。 -- 你知道哪些 编程范型,它们对 JavaScript开发者来有什么用? +- 你知道哪些 编程范型,它们对 JavaScript 开发者来有什么用? - 什么是函数式编程? - 面向对象的核心是什么?传统类继承和原型继承的区别在哪里? - 函数式编程(FP)和面向对象编程(OO)各自优点和不足是什么? diff --git a/docs/topics/algorthimn/transformer.md b/docs/topics/algorthimn/transformer.md new file mode 100644 index 0000000..61996bb --- /dev/null +++ b/docs/topics/algorthimn/transformer.md @@ -0,0 +1,127 @@ +# 数据结构转换 + +## 岗位信息 + +- 公司:阿里 cbu +- 职级:P5 +- 轮次:笔试题 + +## 题目描述 + +将形如: `[0, "a", 1, "b", 2, "c", 3, "e", 2, "d", 1, "x", 0, "ff"]` 的一个数组转化为如下的数据。 + +```js +{ + a: { + b: { + c: { + e: null, + }, + d: null, + }, + x: null, + }, + ff: null, +}; +``` + +## 前置知识 + +- 暂无 + +## 思路 + +题目描述的不是很清晰。但通过观察发现应该是: + +- 每两个一组。这两个中的第一个是深度,第二个是 key。 +- 相同的深度不一定父节点相同。也就是说一个节点的父节点并不是深度-1 的节点,因此深度-1 的节点可能有多个。实际上一个节点的父节点应该是离它最近的深度-1 的节点。 + +根据以上信息,我们可以使用递归来完成。 + +定义函数 DFS(A, start, d),其中 A 为题目输入的数组,start 为当前遍历的索引,方便后续退出递归,d 则是一个用于深度信息的数据结构,形如: + +```js +{ + -1: { + ... + }, + 0: { + ... + }, + 1: { + ... + }, + ... +} + +``` + +key 为深度,value 为深度所对应的对象。因此我们只需要返回 d[-1] 即可。 + +有了上述信息,不难写出如下代码: + +```js +function dfs(A, start, d) { + if (start + 1 >= A.length) return; + // do something + dfs(A, start + 2, d); +} + +function deserialization(A) { + const d = {}; + dfs(A, 0, d); + return d[-1]; +} +``` + +接下来,我们只需要完成状态转移就好了。我们要做的就是将当前的 value 挂到父节点,并将当前节点更新到 d 中即可。 + +## 关键点 + +- 理解题意 + +## 代码 + +```js +function dfs(A, start, d) { + if (start + 1 >= A.length) return; + const [depth, v] = [A[start], A[start + 1]]; + if (d[depth - 1] == void 0) { + d[depth - 1] = {}; + } + let next = {}; + if ( + start + 2 >= A.length || + (start + 2 < A.length && A[start + 2] < A[start]) + ) + next = null; + d[depth - 1][v] = next; + d[depth] = next; + dfs(A, start + 2, d); +} + +function deserialization(A) { + const d = {}; + dfs(A, 0, d); + return d[-1]; +} + +deserialization([0, "a", 1, "b", 2, "c", 3, "e", 2, "d", 1, "x", 0, "ff"]); +``` + +上面代码会输出: + +```js +{ + a: { + b: { + c: { + e: null, + }, + d: null, + }, + x: null, + }, + ff: null, +}; +``` From aedaf19f8b311e9bb80a69d39766cc76a87e2e50 Mon Sep 17 00:00:00 2001 From: lucifer Date: Sat, 23 Jan 2021 12:15:32 +0800 Subject: [PATCH 07/13] fix: typo --- docs/topics/algorthimn/transformer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/algorthimn/transformer.md b/docs/topics/algorthimn/transformer.md index 61996bb..f6e1294 100644 --- a/docs/topics/algorthimn/transformer.md +++ b/docs/topics/algorthimn/transformer.md @@ -34,7 +34,7 @@ 题目描述的不是很清晰。但通过观察发现应该是: - 每两个一组。这两个中的第一个是深度,第二个是 key。 -- 相同的深度不一定父节点相同。也就是说一个节点的父节点并不是深度-1 的节点,因此深度-1 的节点可能有多个。实际上一个节点的父节点应该是离它最近的深度-1 的节点。 +- 相同的深度不一定父节点相同。也就是说一个节点的父节点并不是深度-1 的节点,因为深度-1 的节点可能有多个。实际上一个节点的父节点应该是离它最近的深度-1 的节点。 根据以上信息,我们可以使用递归来完成。 From fe49309eabcf56bca7897a814561f1eb9aa10d1c Mon Sep 17 00:00:00 2001 From: lucifer Date: Sat, 23 Jan 2021 12:18:26 +0800 Subject: [PATCH 08/13] =?UTF-8?q?fix:=20=E8=AF=AD=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/topics/algorthimn/transformer.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/topics/algorthimn/transformer.md b/docs/topics/algorthimn/transformer.md index f6e1294..d4f2ff4 100644 --- a/docs/topics/algorthimn/transformer.md +++ b/docs/topics/algorthimn/transformer.md @@ -34,11 +34,11 @@ 题目描述的不是很清晰。但通过观察发现应该是: - 每两个一组。这两个中的第一个是深度,第二个是 key。 -- 相同的深度不一定父节点相同。也就是说一个节点的父节点并不是深度-1 的节点,因为深度-1 的节点可能有多个。实际上一个节点的父节点应该是离它最近的深度-1 的节点。 +- 相同的深度不一定父节点相同。也就是说一个节点的父节点并不是深度-1 的节点,因为深度-1 的节点可能有多个。实际上一个节点的父节点应该是左侧离它最近的深度-1 的节点。 根据以上信息,我们可以使用递归来完成。 -定义函数 DFS(A, start, d),其中 A 为题目输入的数组,start 为当前遍历的索引,方便后续退出递归,d 则是一个用于深度信息的数据结构,形如: +定义函数 dfs(A, start, d),其中 A 为题目输入的数组,start 为当前遍历的索引,方便后续退出递归,d 则是一个用于存储深度信息的对象,形如: ```js { @@ -56,7 +56,9 @@ ``` -key 为深度,value 为深度所对应的对象。因此我们只需要返回 d[-1] 即可。 +其中 key 为深度,value 为深度所对应的对象。因此我们只需要返回 d[-1] 即可。 + +> -1 表示 0 层的父节点,你可以将其看成是一个虚拟节点, 其作用仅仅是简化逻辑判断。 有了上述信息,不难写出如下代码: @@ -74,7 +76,7 @@ function deserialization(A) { } ``` -接下来,我们只需要完成状态转移就好了。我们要做的就是将当前的 value 挂到父节点,并将当前节点更新到 d 中即可。 +接下来,我们只需要完成状态转移就好了。具体来说就是**将当前的 value 挂到父节点,并将当前节点更新到 d 中即可**。 ## 关键点 From 3ff8fbd698cc94e75fefca6369cf45e1a25d4d3c Mon Sep 17 00:00:00 2001 From: lucifer Date: Mon, 25 Jan 2021 16:23:35 +0800 Subject: [PATCH 09/13] feat: react & vue --- docs/README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 68975fe..e4b68db 100644 --- a/docs/README.md +++ b/docs/README.md @@ -364,6 +364,10 @@ React 考察的点就那么几点,从简单的生命周期,特定 API 的使 - React 的虚拟 DOM diff 算法一定比直接操作 DOM 快么?为什么? - React 虚拟 DOM diff 的算法时间复杂度是多少?为什么? +上面的问题只是冰山一角,社区有很多 React 的问题,比如 [React-Questions](https://github.com/harryheman/React-Questions)。你可以用这些问题来检验自己。 + +> React-Questions 暂时只有俄语版本,你可以使用翻译软件一键翻译。不过体验肯定不太好,期待翻译吧。 + #### Redux 官方给出的介绍是“Redux is a predictable state container for JavaScript apps.”。 @@ -376,7 +380,13 @@ Redux 中核心就是一个单一的 state。state 通过闭包的形式存放 #### Vue -> TODO +如果你能自己实现一个 vue ,那么还有什么难道你?我不建议大家漫无目的地看源码。大家可以找一些抓手,比如作者本人的文章,视频质量。 + +这里推荐一个 vue 作者尤雨溪本身亲自下海讲课,手把手教你实现一个 mini-vue。 + +地址:https://www.vuemastery.com/courses/vue3-deep-dive-with-evan-you/vue3-overview + +B 站有免费视频搬运,大家自己搜索吧。 #### Vuex From 21de40f8768b84b2bf1ab60026f3cac619d90ecd Mon Sep 17 00:00:00 2001 From: lucifer Date: Tue, 30 Mar 2021 18:07:07 +0800 Subject: [PATCH 10/13] feat: https://bigfrontend.dev/problem --- docs/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/README.md b/docs/README.md index e4b68db..726cb4f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -264,6 +264,10 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, - [实现 XPath](./topics/algorthimn/xpath.md) - [数据结构转换](./topics/algorthimn/transformer.md) 🆕 +当你刷完这些题,还觉得不过瘾,我推荐你一个**前端刷题** 网站 https://bigfrontend.dev/problem + +这个网站不仅有算法题,还有一些设计题,手写编程题,可以满足前端的手写编程面试的需求。 + ### 浏览器 前端和浏览器是分不开的,关于 JS 的宿主环境的理解的重要性是不亚于框架。关于这个话题,我总结了几篇文章给大家。 From 157455264412c324b54ccdf0a05f2f1d76cea419 Mon Sep 17 00:00:00 2001 From: robot Date: Sat, 7 Jan 2023 21:07:18 +0800 Subject: [PATCH 11/13] go --- docs/topics/ts/ts-config.md | 3 +-- docs/topics/ts/ts-exercises-2.md | 9 +++------ docs/topics/ts/ts-exercises.md | 6 ++---- docs/topics/ts/ts-internal.md | 3 +-- docs/topics/ts/ts-type-system.md | 3 +-- docs/topics/ts/ts-type.md | 3 +-- 6 files changed, 9 insertions(+), 18 deletions(-) diff --git a/docs/topics/ts/ts-config.md b/docs/topics/ts/ts-config.md index b749381..66bce97 100644 --- a/docs/topics/ts/ts-config.md +++ b/docs/topics/ts/ts-config.md @@ -356,7 +356,6 @@ module 是 CommonJS 和 ES6 module 不能知道 outFile,只有是 None, System ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) -公众号【 [力扣加加](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)】 -知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 +公众号【 [力扣加加](https://p.ipic.vip/n8gbxo.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 点关注,不迷路! diff --git a/docs/topics/ts/ts-exercises-2.md b/docs/topics/ts/ts-exercises-2.md index 2125572..b5c059d 100644 --- a/docs/topics/ts/ts-exercises-2.md +++ b/docs/topics/ts/ts-exercises-2.md @@ -43,8 +43,7 @@ TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教 ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj1sl6j7pij31560p4ad7.jpg) -可以和标准答案进行对比。 -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj1slk6r7bj31g30o9djp.jpg) +可以和标准答案进行对比。 ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj1slk6r7bj31g30o9djp.jpg) 并且由于使用了浏览器缓存, 因此无需登录的情况下也可以保证关掉页面,你的答题进度也会保留。 @@ -285,8 +284,7 @@ type UsersApiResponse = ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gjoppxyseyj30js0f4t9m.jpg) -这是明显的使用 **或逻辑关系** 和**泛型进行类型定义**的强烈信号。 -我们可以使用泛型做如下改造: +这是明显的使用 **或逻辑关系** 和**泛型进行类型定义**的强烈信号。我们可以使用泛型做如下改造: ```ts export type ApiResponse = @@ -1492,7 +1490,6 @@ export class ObjectManipulator { ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) -公众号【 [力扣加加](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)】 -知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 +公众号【 [力扣加加](https://p.ipic.vip/n8gbxo.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 点关注,不迷路! diff --git a/docs/topics/ts/ts-exercises.md b/docs/topics/ts/ts-exercises.md index 14895ee..556bb92 100644 --- a/docs/topics/ts/ts-exercises.md +++ b/docs/topics/ts/ts-exercises.md @@ -43,8 +43,7 @@ TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教 ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj1sl6j7pij31560p4ad7.jpg) -可以和标准答案进行对比。 -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj1slk6r7bj31g30o9djp.jpg) +可以和标准答案进行对比。 ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gj1slk6r7bj31g30o9djp.jpg) 并且由于使用了浏览器缓存, 因此无需登录的情况下也可以保证关掉页面,你的答题进度也会保留。 @@ -1025,7 +1024,6 @@ type PowerUser = Omit & { type: "powerUser" }; ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) -公众号【 [力扣加加](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)】 -知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 +公众号【 [力扣加加](https://p.ipic.vip/n8gbxo.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 点关注,不迷路! diff --git a/docs/topics/ts/ts-internal.md b/docs/topics/ts/ts-internal.md index 3d7aca9..b15d1ef 100644 --- a/docs/topics/ts/ts-internal.md +++ b/docs/topics/ts/ts-internal.md @@ -136,5 +136,4 @@ const a: Number = 1; ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) -公众号【 [力扣加加](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)】 -知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 +公众号【 [力扣加加](https://p.ipic.vip/n8gbxo.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 diff --git a/docs/topics/ts/ts-type-system.md b/docs/topics/ts/ts-type-system.md index d0f29c6..ba5aa9a 100644 --- a/docs/topics/ts/ts-type-system.md +++ b/docs/topics/ts/ts-type-system.md @@ -312,7 +312,6 @@ TypeScript 已经做到了足够智能了,以至于你不需要写类型,它 ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) -公众号【 [力扣加加](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)】 -知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 +公众号【 [力扣加加](https://p.ipic.vip/n8gbxo.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 点关注,不迷路! diff --git a/docs/topics/ts/ts-type.md b/docs/topics/ts/ts-type.md index a50db74..b7e5a56 100644 --- a/docs/topics/ts/ts-type.md +++ b/docs/topics/ts/ts-type.md @@ -134,7 +134,6 @@ tsconfig.json 中有两个配置和类型引入有关。 ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) -公众号【 [力扣加加](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)】 -知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 +公众号【 [力扣加加](https://p.ipic.vip/n8gbxo.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 点关注,不迷路! From 2cbd813115850858c0057c73fed52274349af3f6 Mon Sep 17 00:00:00 2001 From: robot Date: Sat, 7 Jan 2023 21:24:14 +0800 Subject: [PATCH 12/13] go --- docs/topics/ts/ts-config.md | 2 +- docs/topics/ts/ts-exercises-2.md | 2 +- docs/topics/ts/ts-exercises.md | 2 +- docs/topics/ts/ts-internal.md | 2 +- docs/topics/ts/ts-type-system.md | 2 +- docs/topics/ts/ts-type.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/topics/ts/ts-config.md b/docs/topics/ts/ts-config.md index 66bce97..d09f301 100644 --- a/docs/topics/ts/ts-config.md +++ b/docs/topics/ts/ts-config.md @@ -356,6 +356,6 @@ module 是 CommonJS 和 ES6 module 不能知道 outFile,只有是 None, System ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) -公众号【 [力扣加加](https://p.ipic.vip/n8gbxo.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 +公众号【 [力扣加加](https://p.ipic.vip/h9nm77.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 点关注,不迷路! diff --git a/docs/topics/ts/ts-exercises-2.md b/docs/topics/ts/ts-exercises-2.md index b5c059d..5529487 100644 --- a/docs/topics/ts/ts-exercises-2.md +++ b/docs/topics/ts/ts-exercises-2.md @@ -1490,6 +1490,6 @@ export class ObjectManipulator { ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) -公众号【 [力扣加加](https://p.ipic.vip/n8gbxo.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 +公众号【 [力扣加加](https://p.ipic.vip/h9nm77.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 点关注,不迷路! diff --git a/docs/topics/ts/ts-exercises.md b/docs/topics/ts/ts-exercises.md index 556bb92..91a6275 100644 --- a/docs/topics/ts/ts-exercises.md +++ b/docs/topics/ts/ts-exercises.md @@ -1024,6 +1024,6 @@ type PowerUser = Omit & { type: "powerUser" }; ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) -公众号【 [力扣加加](https://p.ipic.vip/n8gbxo.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 +公众号【 [力扣加加](https://p.ipic.vip/h9nm77.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 点关注,不迷路! diff --git a/docs/topics/ts/ts-internal.md b/docs/topics/ts/ts-internal.md index b15d1ef..c316400 100644 --- a/docs/topics/ts/ts-internal.md +++ b/docs/topics/ts/ts-internal.md @@ -136,4 +136,4 @@ const a: Number = 1; ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) -公众号【 [力扣加加](https://p.ipic.vip/n8gbxo.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 +公众号【 [力扣加加](https://p.ipic.vip/h9nm77.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 diff --git a/docs/topics/ts/ts-type-system.md b/docs/topics/ts/ts-type-system.md index ba5aa9a..e218e35 100644 --- a/docs/topics/ts/ts-type-system.md +++ b/docs/topics/ts/ts-type-system.md @@ -312,6 +312,6 @@ TypeScript 已经做到了足够智能了,以至于你不需要写类型,它 ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) -公众号【 [力扣加加](https://p.ipic.vip/n8gbxo.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 +公众号【 [力扣加加](https://p.ipic.vip/h9nm77.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 点关注,不迷路! diff --git a/docs/topics/ts/ts-type.md b/docs/topics/ts/ts-type.md index b7e5a56..87e9baf 100644 --- a/docs/topics/ts/ts-type.md +++ b/docs/topics/ts/ts-type.md @@ -134,6 +134,6 @@ tsconfig.json 中有两个配置和类型引入有关。 ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfxro1x125j30oz0dw43s.jpg) -公众号【 [力扣加加](https://p.ipic.vip/n8gbxo.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 +公众号【 [力扣加加](https://p.ipic.vip/h9nm77.jpg)】知乎专栏【 [Lucifer - 知乎](https://www.zhihu.com/people/lu-xiao-13-70)】 点关注,不迷路! From 5508883c731919e0280677b9fa18cb423e951c8c Mon Sep 17 00:00:00 2001 From: robot Date: Sat, 7 Jan 2023 22:03:19 +0800 Subject: [PATCH 13/13] go --- docs/README.md | 18 +++++----- docs/daily/2019-08-06.md | 4 +-- docs/topics/algorthimn/xpath.md | 2 +- docs/topics/browser/defer-async.md | 2 +- docs/topics/browser/event.md | 4 +-- docs/topics/design-pattern/proxy.md | 6 ++-- docs/topics/js/buit-in-types.md | 2 +- docs/topics/js/reference&priority.md | 6 ++-- docs/topics/js/scope&closures.md | 4 +-- docs/topics/mini-program/architecture.md | 8 ++--- docs/topics/network/https.md | 8 ++--- docs/topics/network/net-mask.md | 8 ++--- docs/topics/network/network-model.md | 2 +- docs/topics/network/tcp.md | 4 +-- docs/topics/os/process/intro.md | 2 +- docs/topics/ts/leetcode-interview-ts.md | 8 ++--- docs/topics/ts/ts-config.md | 8 ++--- docs/topics/ts/ts-exercises-2.md | 18 +++++----- docs/topics/ts/ts-exercises.md | 16 ++++----- docs/topics/ts/ts-generics.md | 34 +++++++++---------- docs/topics/ts/ts-internal.md | 18 +++++----- docs/topics/ts/ts-type-system.md | 16 ++++----- docs/topics/ts/ts-type.md | 2 +- docs/topics/wasm/what.md | 10 +++--- .../weex/architecture.md | 2 +- 25 files changed, 106 insertions(+), 106 deletions(-) diff --git a/docs/README.md b/docs/README.md index 726cb4f..fc8d0ec 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # 大前端面试宝典 - 图解前端 -![logo](./assets/imgs/logo.jpg) +![logo](https://p.ipic.vip/jiysdy.jpg) 这是一份自己总结的关于准备前端面试的一个复习汇总项目,项目不定时更新。 这不仅仅是一份用于求职面试的攻略,也是一份前端 er 用来检视自己,实现突破的宝典。 @@ -113,7 +113,7 @@ Job Model 是 一个很重要的参考标准。 当然每个公司的 Job Model 我在这里画了一个简化版本的 Job Modal,大家可以根据自己的实际情况找自己的目标, 不同层级需要掌握的深度和广度是不一样的,大家根据自己的实际情况学习。 -![job-model](./assets/imgs/topics/job-model/job-model.jpg) +![job-model](https://p.ipic.vip/qzv454.jpg) 后续我计划会出一份详细的不同级别需要掌握的技能的列表。 @@ -146,7 +146,7 @@ Job Model 是 一个很重要的参考标准。 当然每个公司的 Job Model ### 自我介绍 -![自我介绍](./assets/imgs/topics/introduction/intro-1.jpg) +![自我介绍](https://p.ipic.vip/n03b65.jpg) 自我介绍是面试的第一个环节,如果表现良好的话不仅会给面试官留下好印象,有利于之后的面试过程, 而且流畅的自我介绍也可以给自己增加信心,让自己发挥地更好。 @@ -305,7 +305,7 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, ### 设计题 🎩 -![设计题](./assets/imgs/topics/design/design-cover.jpg) +![设计题](https://p.ipic.vip/c8n5f1.jpg) 这类题目有时候是给一个情景,有时候是直接让你实现一个轮子,答案也往往是开放式的。需要你对组件和代码设计有一定的基础。这部分主要考察候选人综合实力,思维开放性,思维严密性,做事的方式等。 @@ -343,7 +343,7 @@ JavaScript 是前端基础中的基础了,这里的面试题目层出不穷, ### 框架 🖼️ -![框架](./assets/imgs/topics/framework/framework-cover.png) +![框架](https://p.ipic.vip/x2b8mr.png) 流行的框架当然也是兵家必争之地,如果你能够完全了解大型知名开源框架的代码和架构实现,那绝对是一个加分项。 @@ -425,7 +425,7 @@ B 站有免费视频搬运,大家自己搜索吧。 对于网络这部分,最重要的是要有一个大的概念,下面也会介绍。 -![network-cover](./assets/imgs/topics/network/network-cover.jpg) +![network-cover](https://p.ipic.vip/k047ir.jpg) - [协议森林(大话网络协议)](https://www.cnblogs.com/vamei/archive/2012/12/05/2802811.html)