从 JS 迁移到 TS(写给初学者的笔记)
相比于强类型的语言来说, js
非常动态, 它的弱类型特点既带来了运行时超高的灵活性, 但是也带了开发时的不确定性, 所以后来微软设计了ts
, 在开发阶段对代码进行类型校验, 偏向于强类型, 提供开发时的安全性, 在编译后移除所有的类型信息变回到js
,保留了运行时的弱类型灵活性特点.
本篇笔记针对是从js
到ts
的转变过程, 包含个人的一些建议和想法, 更多的是想帮助你平滑的学习和使用ts
, 此文并非ts
语法教科书, 如果你只对语法感兴趣的话可以直接跳转到「正式的学习资料」这个小节.
# 从「入门」到「跑路」
初期会痛苦, 中期会习惯, 后期会舒服.
# 初期
编辑器不断地给你的代码打上红线, 提示这个地方类型有问题, 而你又挠破脑袋觉得「WFT」要怎么写, 搜索也不知道该搜什么关键词合适, 这个时候痛苦, 同时编码效率也会下降, 以前一天能做完的任务, 现在应该要一天半(多出来的半天是你在挠头弄明白这个地方应该是什么类型).
-
使用别人已经编辑好的一份比较简单的
tsconfig.json
配置文件, 暂时不要尝试自己从头配置(相信我, 完全搞懂配置文件真的让人头大). -
只接触基本语法和用法
string
/boolean
/interface
/type
等, 解决不了的地方如果着急可以先any
, 等更熟悉一点之后再回头把any
一点点改掉(当然了, 我们都不希望看到一个新的anyScript
出现). -
从比较纯粹独立的文件用起, 例如你常写的
utils
函数文件, 复杂的业务逻辑文件先不用动,ts
和js
是可以在一个项目中共存的, 所以不用着急, 慢慢来. -
问使用过
ts
的人, 对于简单的问题, 他们的答案比搜索引擎来的快.
# 中期
你习惯了为自己写下的函数或者变量声明类型, 编码效率也恢复正常, 因为你已经驾轻就熟, 同时配合代码提示自动补全以及类型校验带来的安全感, 你感觉自己无所不能.
-
是时候「浪子回头」把之前的
any
一点点改为更具体的声明了, 良好的声明能够给你编写和重构代码代码带来安全感, 只要编辑器点头了, 那就代表几乎不会有低级错误了(比如english
写成engilsh
, 或者这个对象居然还会undefined
?). -
使用更高级的用法, 比如类型守卫
Type Guard
/<T>
泛型/infer
推断, 开始自己写一些类型推断函数来让你的代码更整洁高效. (ts
本身也可以变得很复杂, 你甚至可以用它来做逻辑运算, 在leetcode上还有题目可以刷, 但是无需担心, 在你日常的使用中接触不到那种复杂度). -
了解
tsconfig.json
的配置, 它决定了你写的ts
代码会被如何编译成js
, 你可以把它类比为webpack
的配置文件(不过比webpack
还是要简单多了, 毕竟有webpack
配置工程师, 但是不会有ts
配置工程师).
# 后期
日常使用已经没有问题, 甚至有了点强迫症, 不上ts
的代码能看?
(别找建议了, 没有, 因为我学的还不够, 反而欢迎给我点建议.)
# 渐进性
渐进性体现在好几个方面, 可以让ts
的学习曲线平滑.
# 不需要一次性把所有.js
文件都改为了.ts
文件
ts
是js
的超集, 也就是说所有的js
代码都是有效的ts
代码, 文件后缀名的修改对你的代码运行不会造成任何影响; 另外二者是可以共存一段时间的, 从小的地方慢慢改起, 比如工具函数, 改造它不会对你的业务造成影响, 当有了信心之后再改剩下的文件.
# 不需要写出所有的类型声明, ts
是智能的
它会自动静态分析你的代码自动推断出合适的类型. 你可以把以下代码拷贝到你的编辑器中, 保存为.ts
后缀文件, 然后鼠标hover变量或者函数, 看看有什么提示(剧透: 它很聪明的!).
const a = 1;
// 1 是数字, 你将一个数字复制给变量a, 于是ts就推断出变量a应当是一个 number (数字)类型的变量, 很聪明吧.
// 不需要你使用 const a: number = 1 这种方式来手动告诉ts a的类型
const b = [1];
// [1] 是一个数组, 它包含一个数字 1, 于是ts就推断出变量b应当是一个包含数字的数组类型.
const c = [1, 'hello'];
// [1, 'hello'] 是一个数组, 它包含一个数字 1 和一个字符串 'hello', 于是ts就推断出变量c应该是一个包含数字或者字符串的数组: (number | string)[]
function add(a: number, b: number) {
return a + b;
}
// 我们这里虽然声明了函数入参a和b的类型, 但是并没有声明函数返回值的类型, 但是你hover函数看看它是不是准确的提示了你返回值是 number 类型, number + number 得到的当然是 number, ts果然很聪明.
如果你思考的多一点, 就会有一个疑惑, 在上面的示例中const b = [1]
自动推断的结果为什么是number[]
, 而不是[number]
(这两者是有区别的, 前者只是说这是仅包含数字的数组, 后者则限定了是仅包含一个数字的数组), 这是因为ts
有一套自己的规则(the best common type
)来决定最后推断的结果, 这是经过思考和实践后得到的一个比较合适的规则, 在日常使用中能为我们带来最大的便利.
如果你发现它自动推断的结果不是你想要的, 比如虽然初始化时const b = [1]
, 但是实际上b
之后有可能会被b.push('hello')
, 也就是b
实际是可能存在其他类型值的, 那么我们就需要const b:(number | string)[] = [1]
手动声明b
的类型, 这就是告诉ts
: 你别忙活啦, 我比你懂代码, b的类型应该是 (number | string)[]. 之后ts
就会按照你声明的类型来对代码进行校验.
# 那么什么时候需要手动声明类型呢?
-
刚才提到一个词「**静态分析 **」, 自动推导依赖的是静态的代码, 它无法分析任何运行时的信息, 它从有限的静态代码中得到的信息也是有限的, 所以推导的结果也是也有限的, 不一定完全符合你的预期, 当你在设计代码的时候你是最大的知情人, 你知道类型应该是什么样的, 所以如果它推导不准确, 那么手动声明吧, 你就是标杆!
-
我们常常希望一件事物是稳定存在的(就像你发誓你的爱不会变心一样), 自动推导则意味着随着代码的修改, 推导的结果可能会变
// 此时的函数签名推导结果是 function add(a: number, b: number): number export function add(a: number, b: number) { return a + b; } // 某一天我们在重构这个函数的时候"意外"把结果转为 string 返回 // 这次函数签名推导结果自动变成了 function add(a: number, b: number): string export function add(a: number, b: number) { return String(a + b); }
如果你是一个工具函数或者库的提供方, 对你来说没有影响, 你的
ts
会工作的很好, 你也可以正常的发布和更新你的工具包, 你感觉不到这个"意外"的存在(当然, 如果你有完善的单测的话就当我没说), 但是你的使用方可倒了大霉了, 函数的返回值类型发生了变化, 他们的代码中有可能因此发生致命错误.为了避免这种情况的发生, 我们就可以给函数提前写上类型声明, 来确保我们在维护代码过程中
api
是稳定的.export function add(a: number, b: number): number { return a + b; } // 此时如果你犯了这种小错误, ts 会毫不犹豫的告诉你函数的返回值类型错误, 该检查你改过的代码了. export function add(a: number, b: number): number { // Type 'string' is not assignable to type 'number'. return String(a + b); }
# 不需要一定去把源码改为ts
有很多代码不在我们的控制范围内, 比如社区一些比较早期的npm包(lodash
等), 或者我们内部的一些npm包, 希望在引入使用的时候能够得到包里面api
的代码提示, 但是又不好去改源码, 我们就可以单独编写与之适配的声明文件来和包本身配合使用
社区里存在大量@types/xxx
(例如@types/lodash
)的包, 里面其实就是一份声明文件, 指定与之适配的包有哪些api
, 这种包对运行时没有任何影响, 也不会打包到编译结果中, 所以都是以--save-dependencies
的方式安装的.
如果源码是js
又没有现成的与之适配的@types/xxx
类型包, 那么我们也可以在项目中使用declare
方式手动为其编写api
声明, 来让使用的时候可以有代码提示和校验.
需要注意的是: 这种情况不是最优解, 而是在无法变更源码的情况下做出的适配妥协, 它的缺点是需要和源包配套使用, api
的声明和实际实现保持一致, 如果某次源包做了升级, 修改了某个api
, 那么声明包也需要跟着升级修改api
的声明, 否则就会出现声明和实现不一致的情况(错误的ts
提示比没有提示更糟糕), 所以在条件允许的情况下对源工程进行一下升级吧, Let's TS it!.
# 正式的学习资料
TypeScript
于2012年10月首次发布0.8版本, 经过了近10年的迭代和发展到现在的4.5
版本, 也经过了和flow
的争锋(比如尤雨溪直呼「压错宝」), 如今已经是大势所趋, 也形成了一个很繁荣和稳定的社区.
看完了上面很多我个人的理解和想法, 你最关心的应该是这个章节, 不过你可能会有点失望, 因为社区里文章教程已是汗牛充栋, 我再写也是关公门前耍大刀不会写的比他们更好, 所以我列出了学习过程中收藏的比较好的文章供大家查阅.
-
深入理解 TypeScript, 这是 《TypeScript Deep Dive》 的中文翻译版, 初学者额从概览开始看起会比较好入门, 里面也有一些高级的用法可供查阅, 建议通读.
此书是 《TypeScript Deep Dive》 的中文翻译版,感谢作者 Basarat 的付出。
-
TypeScript 高级用法, 这篇笔记适用于在了解了基础用法入门后查阅, 是日常使用经验的总结, 简洁实用.
本文主要介绍 TypeScript 的高级用法,适用于对 TypeScript 已经有所了解或者已经实际用过一段时间的同学,分别从类型、运算符、操作符、泛型的角度来系统介绍常见的 TypeScript 文章没有好好讲解的功能点,最后再分享一下自己的实践经历。
作者:字节前端 链接:https://juejin.cn/post/6926794697553739784 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
-
tsconfig.json
工程配置文件, 这篇笔记适用于tsconfig.json
配置工程师(开玩笑, 暂时没有这个职位), 详细讲解了各个配置字段的函数, 方便理解和查阅, 完整的官方索引配置可以查看这里.tsconfig.json
是 TypeScript 项目的配置文件,如果一个目录下存在一个tsconfig.json
文件,那么它意味着这个目录是 TypeScript 项目的根目录。TypeScript 编译器编译代码之前,首先读取这个配置文件,并根据其中的属性来设置 TypeScript 项目的编译参数。