TypeScript从入门到入土

为什么要学习 TypeScript

TypeScript 的核心思想其实很简单:

在 JavaScript 上加一层静态类型系统。

以前很多错误只能在运行时发现,例如:

1
2
3
4
5
function add(a, b) {
return a + b
}

add(1, "2")

JavaScript 会愉快地返回:

1
"12"

而 TypeScript 会在编译阶段就提醒你:

c*m的s*东西,你写了个什么**玩意。

换句话说:

以前要在运行时踩的坑,现在编译器会提前帮你踩一遍。

臭写代码的实际工作量并没有减少,只是痛苦提前发生了。


基础语法

TypeScript 是 JavaScript 的 superset

也就是说:

几乎所有 JavaScript 代码都是合法的 TypeScript。

你只是在 JavaScript 的基础上,增加了类型。

例如:

1
2
let a: number = 10
let b: string = "hello"

常见类型

最基础的类型有:

1
2
3
number
string
boolean

示例:

1
2
3
let age: number = 18
let name: string = "Alice"
let ok: boolean = true

如果你不写类型,TypeScript 也会自动推断:

1
let x = 10

此时 x 会被推断为 number


数组

数组可以写成两种方式:

1
2
3
let a: number[] = [1,2,3]

let b: Array<number> = [1,2,3]

这两种写法是完全等价的。


元组

元组(tuple)是一种 固定长度、固定类型顺序 的数组。

1
2
3
let person: [string, number]

person = ["Alice", 18]

如果顺序写错,TypeScript 会报错。


字符串

茴香豆的“茴”有四种写法,TypeScript 的字符串有三种写法。

1
2
3
"hello"
'hello'
`hello`

前两种只是风格不同。

第三种是 模板字符串

模板字符串支持表达式:

1
2
3
let name = "Alice"

let msg = `hello ${name}`

拼接后,msg 的值是 "hello Alice"

这在现代项目中非常常见。尤其是模板字符串还支持多行写法,我的评价是只要写过 HTML 的都知道含金量。

1
2
3
4
let html = `
<div>
<p>Hello</p>
</div>`

条件语句

条件语句和 JavaScript 完全一样:

1
2
3
if (a > 10) {
console.log("big")
}

但需要注意:

永远优先使用 === 而不是 ==

因为 == 会发生隐式类型转换。

1
"5" == 5   // true

这通常不是你想要的行为。喜欢这么写的人已经被测试工程师细细地切做臊子了。


循环

常见循环包括:

1
2
3
for
while
for...of

例如:

1
2
3
for (const x of [1,2,3]) {
console.log(x)
}

for...of 用来遍历 值(value)

for...in 遍历的是 键(key / index)

很多新手会混淆这两者。


一个具体例子

1
2
3
4
5
const arr = ["apple", "banana", "orange"]

for (const i in arr) {
console.log(i)
}

输出:

1
2
3
0
1
2

因为 for...in 遍历的是 数组的下标(键)

如果改成:

1
2
3
for (const fruit of arr) {
console.log(fruit)
}

输出:

1
2
3
apple
banana
orange

因为 for...of 遍历的是 数组里的值


函数

函数可以显式声明参数类型和返回类型。

1
2
3
function add(a: number, b: number): number {
return a + b
}

还有一种推导式写法:

1
2
3
const add = (a: number, b: number): number => {
return a + b
}

如果返回类型省略,TypeScript 也会自动推断。


类型系统

如果你学过 C++,那么阅读一下以下的两段代码:

1
2
3
4
struct User {
string name;
int age;
};
1
2
3
4
interface User {
name: string
age: number
}

看起来很像对吧?

两者的相似点是:

  • 都用于描述对象的结构
  • 都可以用来约束数据的形状

但关键区别是:

  • C++ 的 struct 是运行时存在的数据结构
  • TypeScript 的 interface 只是编译期的类型检查

编译成 JavaScript 后,interface 会完全消失。


那为什么还有 type?

很多人接触 TypeScript 时会疑惑:既生 interface,何生 type

简单理解:

  • interface:主要用于描述 对象结构
  • type:是 更通用的类型别名

例如 type 可以做到:

1
type ID = string | number

或者:

1
2
3
4
type Point = {
x: number
y: number
}

也就是说:

interface 更像“对象结构定义”,而 type 是“任意类型的别名”。

泛型

泛型解决的问题其实很简单:

写一次代码,但适用于很多类型。

例如:

1
2
3
function identity<T>(x: T): T {
return x
}

使用时:

1
2
identity<number>(5)
identity<string>("hello")

TypeScript 支持 class:

1
2
3
4
5
6
7
class Person {
name: string

constructor(name: string) {
this.name = name
}
}

这和现代 JavaScript 基本一致。


异步编程

异步编程是一种通过非阻塞方式执行任务的编程范式,调用者发出请求后无需等待结果,转而通过事件通知、状态轮询或回调函数处理后续操作。其核心目标是提升程序执行效率,适用于I/O密集型任务和高并发场景。(百度百科)

叽里咕噜说什么呢

让我们说中文:假设现在你想去餐厅吃饭。

同步的世界里是这样的:

  1. 点餐
  2. 等待做菜
  3. 吃饭
  4. 离开

你必须一直站在柜台前等。

而异步的世界可以:

  1. 点餐
  2. 拿到号码牌
  3. 去座位上玩手机
  4. 叫号时回来取餐

这个 号码牌 就很像 Promise。

1
const p = fetch("/api/data")

你拿到了一个 Promise。

顾名思义,代码给出了一个“承诺”,它表示:

虽然我暂时没有结果,但我承诺结果未来会到达。


async / await

为了让代码更像同步写法,JavaScript 引入了 async / await

1
2
3
4
5
6
7
async function run() {

const data = await fetch("/api")

console.log(data)

}

await 的意思其实很直白:

等一会儿。

但注意:

它不会阻塞整个程序。

只会暂停当前函数。


Node.js 中使用 TS

通常需要:

1
2
3
typescript
@types/node
ts-node

安装:

1
npm install typescript ts-node @types/node

然后运行:

1
npx ts-node main.ts

项目结构

一个典型的 TypeScript 项目可能是这样:

1
2
3
4
5
6
7
project
src
main.ts
utils
math.ts
tsconfig.json
package.json

实战案例:自动预约研修室脚本

前面的内容大多是语法,下面给各位老吃家上一坨我拉的大的品鉴品鉴。

场景:

很多学校的自习室 / 研修间需要在线预约,而且每天固定时间开放预约。

于是很多同学都会遇到一个问题:

🦌少了手速不够快。

这时候,一个简单的脚本就可以帮你自动完成:

  • 查询房间
  • 选择优先房间
  • 等待开放时间
  • 自动提交预约

下面是一个 简化且隐去敏感信息后的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import axios from "axios"
import readline from "readline"
// 保存用户输入的登录 cookie
let sessionCookie = ""
// 创建命令行输入接口
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
// 从终端读取 cookie,用于后续接口请求认证
rl.question("请输入登录 cookie: ", (cookie: string) => {
sessionCookie = cookie
rl.close()
main()
})
/**
* 查询指定日期的可预约房间
* @param date 日期字符串,例如 20260310
*/
async function getRoomList(date: string): Promise<any[]> {
const url = `https://example.edu/api/reserve?date=${date}`
const res = await axios.get(url, {
headers: {
// 使用登录 cookie 进行身份验证
Cookie: `session=${sessionCookie}`,
"User-Agent": "Mozilla/5.0"
}
})
// 返回房间列表
return res.data.data
}
/**
* 尝试预约指定房间
* @param start 开始时间
* @param end 结束时间
* @param roomId 房间 ID
*/
async function reserveRoom(start: Date,end: Date,roomId:number){
// 预约请求体
const payload = {
// ISO 时间格式(服务器接口要求)
resvBeginTime:start.toISOString(),
resvEndTime:end.toISOString(),
// 要预约的房间
resvDev:[roomId],
// 备注
note:"学习讨论"
}
const res = await axios.post(
"https://example.edu/api/reserve",
payload,
{
headers:{
Cookie:`session=${sessionCookie}`
}
}
)
return res.data
}
/**
* 简单的 sleep 函数
* 用于等待一段时间
*/
function sleep(ms:number){
return new Promise(resolve=>setTimeout(resolve,ms))
}
async function main(){
// 预约系统只允许预约两天后的日期
const today = new Date()
today.setDate(today.getDate()+2)
// 生成接口需要的日期格式:YYYYMMDD
const dateString = today
.toISOString()
.slice(0,10)
.replace(/-/g,"")
console.log("查询日期:",dateString)
// 获取房间列表
const rooms = await getRoomList(dateString)
// 优先选择的房间
const preferredRooms = [
"研修间A",
"研修间B",
"研修间C"
]
// 将优先房间排到前面
const sorted = rooms.sort((a,b)=>{
const pa = preferredRooms.includes(a.roomName)
const pb = preferredRooms.includes(b.roomName)
if(pa && !pb) return -1
if(!pa && pb) return 1
return 0
})
const now = new Date()
// 预约开放时间(17:59:50)
const openTime = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate(),
17,59,50
)
const wait = openTime.getTime()-now.getTime()
if(wait>0){
console.log("等待预约开放…")
// 等待到预约开放时间
await sleep(wait)
}
// 选择排序后的第一个房间
const room = sorted[0]
// 预约时间段:19:00 - 22:00
const start = new Date()
start.setHours(19,0,0,0)
const end = new Date()
end.setHours(22,0,0,0)
// 不断尝试预约,直到成功
while(true){
const res = await reserveRoom(start,end,room.devId)
if(res && res.success){
console.log("预约成功")
break
}
console.log("失败,1秒后重试…")
await sleep(1000)
}
}

这个脚本其实综合使用了前面学到的很多知识:

  • async / await
  • Promise
  • HTTP 请求
  • 数组排序
  • 时间处理

也说明了一件事:

TypeScript 不只是写前端页面。

它同样可以写各种自动化脚本。

很多开发者甚至会用它替代 Python 来写一些小工具比如我这个不会写 Python 的哈皮


常见坑位

学习 TypeScript 时,有几个坑几乎所有人都会踩一遍。

any 的诱惑

any 的意思其实很简单:

关闭类型检查。

1
2
3
4
let x: any = 10

x = "hello"
x = true

编译器不会再管你。

短期来看很舒服。

长期来看——

你基本就回到了 JavaScript 的世界

因此从工程严谨性和可维护性角度考虑:

能不用 any 就尽量不用。

但是,工程中也有一些高手,能做到出现 bug 后 10 分钟内定位到问题代码行并修复,你要是也能做到,那 any 也不是不能用。

如果不确定自己的 debug 水平,可以找我要一个我早期写的烂代码示例,如果你能从 4k 行的代码里快速定位到引发 bug 的 any,那我永久授予你“Any Master”称号,允许你随意使用 any


unknown vs any

unknown 可以理解为:

我不知道这个类型是什么。

但和 any 不同的是,你必须先检查才能使用

1
2
3
4
5
let value: unknown = "hello"

if (typeof value === "string") {
console.log(value.toUpperCase())
}

这种设计其实非常安全。


类型断言

有时候你比编译器更清楚类型。

可以使用 类型断言

1
const input = document.querySelector("#input") as HTMLInputElement

但需要记住:

类型断言不会改变运行时行为。

它只是告诉编译器:

“相信我,我知道自己在干什么。”

如果你判断错了,运行时仍然会出错。


never

never 表示:

永远不会发生的类型。

例如:

1
2
3
function fail(): never {
throw new Error("error")
}

函数永远不会返回。


tsconfig.json 关键配置

几乎所有 TypeScript 项目都会有一个文件:

1
tsconfig.json

一个典型配置可能是:

1
2
3
4
5
6
7
8
9
10
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist"
}
}

几个比较重要的选项:

strict

1
"strict": true

开启后会启用一整套严格类型检查。

绝大多数现代项目都会打开。


target

1
"target": "ES2020"

表示编译后的 JavaScript 版本。


outDir

1
"outDir": "dist"

编译后的 JS 文件输出目录。


一个典型的 TypeScript 项目

很多新手第一次接触 TS 项目时会有点迷茫:

文件应该怎么组织?

一个简单结构通常是:

1
2
3
4
5
6
7
8
9
10
11
12
project
src
index.ts
api
client.ts
utils
time.ts

dist

package.json
tsconfig.json

开发时写的是 src 里的 TS。

编译后生成 dist 里的 JS。


TypeScript 知识地图

学到这里,可以简单整理一下 TypeScript 的核心内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
基础语法
├─ number / string / boolean
├─ array / tuple
└─ function

类型系统
├─ type
├─ interface
├─ union
└─ generic

面向对象
└─ class

异步编程
├─ Promise
└─ async / await

工程能力
├─ tsconfig
├─ Node.js
└─ 项目结构

如果你看到这里还没有被劝退,并掌握了大部分内容,基本已经能在大多数项目中 正常使用 TypeScript


总结

如果你觉得写 JavaScript 是一种折磨,TypeScript 可能可以让你从一种折磨换成另一种折磨。

但至少:

当项目规模变大时,

TypeScript 往往会让代码更容易维护。

最后是古希腊掌管全栈开发的神的留言:

小项目:TypeScript 似乎有点麻烦。

大项目:没有 TypeScript 才是真麻烦。

祝你写代码顺利,也祝你的研修室永远能抢到。