TypeScript从入门到入土
为什么要学习 TypeScript
TypeScript 的核心思想其实很简单:
在 JavaScript 上加一层静态类型系统。
以前很多错误只能在运行时发现,例如:
1 | function add(a, b) { |
JavaScript 会愉快地返回:
1 | "12" |
而 TypeScript 会在编译阶段就提醒你:
c*m的s*东西,你写了个什么**玩意。
换句话说:
以前要在运行时踩的坑,现在编译器会提前帮你踩一遍。
臭写代码的实际工作量并没有减少,只是痛苦提前发生了。
基础语法
TypeScript 是 JavaScript 的 superset。
也就是说:
几乎所有 JavaScript 代码都是合法的 TypeScript。
你只是在 JavaScript 的基础上,增加了类型。
例如:
1 | let a: number = 10 |
常见类型
最基础的类型有:
1 | number |
示例:
1 | let age: number = 18 |
如果你不写类型,TypeScript 也会自动推断:
1 | let x = 10 |
此时 x 会被推断为 number。
数组
数组可以写成两种方式:
1 | let a: number[] = [1,2,3] |
这两种写法是完全等价的。
元组
元组(tuple)是一种 固定长度、固定类型顺序 的数组。
1 | let person: [string, number] |
如果顺序写错,TypeScript 会报错。
字符串
茴香豆的“茴”有四种写法,TypeScript 的字符串有三种写法。
1 | "hello" |
前两种只是风格不同。
第三种是 模板字符串。
模板字符串支持表达式:
1 | let name = "Alice" |
拼接后,msg 的值是 "hello Alice"。
这在现代项目中非常常见。尤其是模板字符串还支持多行写法,我的评价是只要写过 HTML 的都知道含金量。
1 | let html = ` |
条件语句
条件语句和 JavaScript 完全一样:
1 | if (a > 10) { |
但需要注意:
永远优先使用 === 而不是 ==。
因为 == 会发生隐式类型转换。
1 | "5" == 5 // true |
这通常不是你想要的行为。喜欢这么写的人已经被测试工程师细细地切做臊子了。
循环
常见循环包括:
1 | for |
例如:
1 | for (const x of [1,2,3]) { |
for...of 用来遍历 值(value)。
而 for...in 遍历的是 键(key / index)。
很多新手会混淆这两者。
一个具体例子
1 | const arr = ["apple", "banana", "orange"] |
输出:
1 | 0 |
因为 for...in 遍历的是 数组的下标(键)。
如果改成:
1 | for (const fruit of arr) { |
输出:
1 | apple |
因为 for...of 遍历的是 数组里的值。
函数
函数可以显式声明参数类型和返回类型。
1 | function add(a: number, b: number): number { |
还有一种推导式写法:
1 | const add = (a: number, b: number): number => { |
如果返回类型省略,TypeScript 也会自动推断。
类型系统
如果你学过 C++,那么阅读一下以下的两段代码:
1 | struct User { |
1 | interface User { |
看起来很像对吧?
两者的相似点是:
- 都用于描述对象的结构
- 都可以用来约束数据的形状
但关键区别是:
- C++ 的 struct 是运行时存在的数据结构
- TypeScript 的 interface 只是编译期的类型检查
编译成 JavaScript 后,interface 会完全消失。
那为什么还有 type?
很多人接触 TypeScript 时会疑惑:既生 interface,何生 type?
简单理解:
interface:主要用于描述 对象结构type:是 更通用的类型别名
例如 type 可以做到:
1 | type ID = string | number |
或者:
1 | type Point = { |
也就是说:
interface更像“对象结构定义”,而type是“任意类型的别名”。
泛型
泛型解决的问题其实很简单:
写一次代码,但适用于很多类型。
例如:
1 | function identity<T>(x: T): T { |
使用时:
1 | identity<number>(5) |
类
TypeScript 支持 class:
1 | class Person { |
这和现代 JavaScript 基本一致。
异步编程
异步编程是一种通过非阻塞方式执行任务的编程范式,调用者发出请求后无需等待结果,转而通过事件通知、状态轮询或回调函数处理后续操作。其核心目标是提升程序执行效率,适用于I/O密集型任务和高并发场景。(百度百科)
叽里咕噜说什么呢
让我们说中文:假设现在你想去餐厅吃饭。
同步的世界里是这样的:
- 点餐
- 等待做菜
- 吃饭
- 离开
你必须一直站在柜台前等。
而异步的世界可以:
- 点餐
- 拿到号码牌
- 去座位上玩手机
- 叫号时回来取餐
这个 号码牌 就很像 Promise。
1 | const p = fetch("/api/data") |
你拿到了一个 Promise。
顾名思义,代码给出了一个“承诺”,它表示:
虽然我暂时没有结果,但我承诺结果未来会到达。
async / await
为了让代码更像同步写法,JavaScript 引入了 async / await。
1 | async function run() { |
await 的意思其实很直白:
等一会儿。
但注意:
它不会阻塞整个程序。
只会暂停当前函数。
Node.js 中使用 TS
通常需要:
1 | typescript |
安装:
1 | npm install typescript ts-node @types/node |
然后运行:
1 | npx ts-node main.ts |
项目结构
一个典型的 TypeScript 项目可能是这样:
1 | project |
实战案例:自动预约研修室脚本
前面的内容大多是语法,下面给各位老吃家上一坨我拉的大的品鉴品鉴。
场景:
很多学校的自习室 / 研修间需要在线预约,而且每天固定时间开放预约。
于是很多同学都会遇到一个问题:
🦌少了手速不够快。
这时候,一个简单的脚本就可以帮你自动完成:
- 查询房间
- 选择优先房间
- 等待开放时间
- 自动提交预约
下面是一个 简化且隐去敏感信息后的示例。
1 | import axios from "axios" |
这个脚本其实综合使用了前面学到的很多知识:
async / awaitPromiseHTTP 请求数组排序时间处理
也说明了一件事:
TypeScript 不只是写前端页面。
它同样可以写各种自动化脚本。
很多开发者甚至会用它替代 Python 来写一些小工具比如我这个不会写 Python 的哈皮。
常见坑位
学习 TypeScript 时,有几个坑几乎所有人都会踩一遍。
any 的诱惑
any 的意思其实很简单:
关闭类型检查。
1 | let x: any = 10 |
编译器不会再管你。
短期来看很舒服。
长期来看——
你基本就回到了 JavaScript 的世界。
因此从工程严谨性和可维护性角度考虑:
能不用
any就尽量不用。
但是,工程中也有一些高手,能做到出现 bug 后 10 分钟内定位到问题代码行并修复,你要是也能做到,那 any 也不是不能用。
如果不确定自己的 debug 水平,可以找我要一个我早期写的烂代码示例,如果你能从 4k 行的代码里快速定位到引发 bug 的 any,那我永久授予你“Any Master”称号,允许你随意使用 any。
unknown vs any
unknown 可以理解为:
我不知道这个类型是什么。
但和 any 不同的是,你必须先检查才能使用。
1 | let value: unknown = "hello" |
这种设计其实非常安全。
类型断言
有时候你比编译器更清楚类型。
可以使用 类型断言:
1 | const input = document.querySelector("#input") as HTMLInputElement |
但需要记住:
类型断言不会改变运行时行为。
它只是告诉编译器:
“相信我,我知道自己在干什么。”
如果你判断错了,运行时仍然会出错。
never
never 表示:
永远不会发生的类型。
例如:
1 | function fail(): never { |
函数永远不会返回。
tsconfig.json 关键配置
几乎所有 TypeScript 项目都会有一个文件:
1 | tsconfig.json |
一个典型配置可能是:
1 | { |
几个比较重要的选项:
strict
1 | "strict": true |
开启后会启用一整套严格类型检查。
绝大多数现代项目都会打开。
target
1 | "target": "ES2020" |
表示编译后的 JavaScript 版本。
outDir
1 | "outDir": "dist" |
编译后的 JS 文件输出目录。
一个典型的 TypeScript 项目
很多新手第一次接触 TS 项目时会有点迷茫:
文件应该怎么组织?
一个简单结构通常是:
1 | project |
开发时写的是 src 里的 TS。
编译后生成 dist 里的 JS。
TypeScript 知识地图
学到这里,可以简单整理一下 TypeScript 的核心内容。
1 | 基础语法 |
如果你看到这里还没有被劝退,并掌握了大部分内容,基本已经能在大多数项目中 正常使用 TypeScript。
总结
如果你觉得写 JavaScript 是一种折磨,TypeScript 可能可以让你从一种折磨换成另一种折磨。
但至少:
当项目规模变大时,
TypeScript 往往会让代码更容易维护。
最后是古希腊掌管全栈开发的神的留言:
小项目:TypeScript 似乎有点麻烦。
大项目:没有 TypeScript 才是真麻烦。
祝你写代码顺利,也祝你的研修室永远能抢到。