从 JavaScript 到 TypeScript一份面向实战的入门与迁移指南
·
1、JavaScript 简史与痛点回顾
JS 从“网页里写点小脚本”一路进化到支撑数十万行的复杂前端与 Node.js 服务端应用。早期设计的“轻快”带来了不少怪癖,规模一大就变成维护成本:
// 1. == 会做隐式类型转换,结果常常反直觉
"" == 0 // true
false == [] // true
0 == '0' // true
// 2. 链式比较不是你以为的数学意义
let x = 42
1 < x < 3 // true,对任何 x 都为 true(因为 (1 < x) 先变成 true,再被转成 1,1 < 3)
// 3. 属性拼写错误,运行期才发现
const rect = { width: 10, height: 15 }
const area = rect.width * rect.heigth // NaN(heigth 拼错)
小项目里这些“惊喜”尚能兜住;当代码基数上万,问题就会指数级放大。我们需要在运行前把这类错误挡在门外。
2、TypeScript 是什么(以及不是什么)
- 静态类型检查器:在执行前,基于“值的种类”(类型)检查错误。
- JavaScript 的带类型超集:所有合法 JS 代码,放进 TS 里语法上都是合法的。
- 不改运行时语义:TS 不改变 JS 的运行时行为;除以 0 依然是 Infinity,不会因为“有类型”就抛异常。
- 类型擦除:编译后产出的是没有类型信息的纯 JS;类型只在编译阶段起作用。
- 没有额外运行时库:你用的仍是 JS 的标准库与生态。
为什么这很重要?
这保证了“渐进式迁移”可行:你可以边写边引入类型,不用担心“换语言”带来的运行时差异。
3、TS 与 JS 的关系:语法、类型、运行时
3.1 语法:JS 语法在 TS 中全部合法
// 这段 JS 放进 .ts 文件也能通过语法检查(可能有类型错误,但语法没问题)
const msg = "hello"
console.log(msg)
3.2 类型:在“如何使用值”上增加规则
// 属性拼写:TS 会在编译期报错
const rect = { width: 10, height: 15 }
const area = rect.width * rect.heigth
// ~~~~~~ ~~~~~~
// Property 'heigth' does not exist... Did you mean 'height'?
// 算术:数组参与除法没有意义
console.log(4 / [])
// The right-hand side of an arithmetic operation must be of type 'any' | 'number' | 'bigint' | enum
3.3 运行时:与 JS 一致
// 即使 TS 觉得可疑,运行时行为仍与 JS 完全一致
console.log(1 / 0) // Infinity
4、TS 到底帮你挡住了什么坑?
- 低级错误:拼写、空指针、错误的 API 参数、算术参与者不对等。
- 重构安全:改字段名/函数签名后,所有引用点会立刻红线。
- 接口契约:调用方与实现方的“约定”在类型层面固化,跨模块协作更稳。
- 智能提示:IDE 补全、跳转、重命名、重构的可靠性显著提升。
- 建模能力:用类型表达业务概念(联合、映射、条件、模板字面量类型等),让代码=文档。
5、三条落地路径(从易到难)
路径 A:零成本“加固”你现有的 JS
适合:老代码、短期不便迁移、想获得基础检查与提示。
-
在
.js文件顶部加:// @ts-check -
用 JSDoc 注释补类型:
/** * @param {{width: number, height: number}} rect */ function area(rect) { return rect.width * rect.height } -
VS Code 会启用 TS 检查与智能提示,无需改后缀、无需编译。
路径 B:渐进式迁移到 .ts/.tsx
适合:中型项目,可分模块推进。
- 新增
tsconfig.json,打开渐进策略(见下节)。 - 允许现有 JS 先跑着,逐步把模块改成
.ts,为公共 API 与数据结构先写类型。 - 单测覆盖关键路径,边迁边稳。
路径 C:新项目直接上 TS
适合:从零开始,追求最佳实践。
- 直接用 TS 初始化(例如 Vite/Next 等脚手架的 TS 选项)。
- 一开始就全开严格模式,后面只会更轻松。
6)可复制的 tsconfig.json 基线
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler", // 现代打包器/运行时友好
"jsx": "react-jsx", // 若用 React;否则删除
"strict": true, // 开启所有严格项的集合
"noImplicitAny": true, // 隐式 any 直接报错
"exactOptionalPropertyTypes": true, // 可选属性的精确语义
"noUncheckedIndexedAccess": true, // arr[i] 可能是 undefined
"noFallthroughCasesInSwitch": true,
"useUnknownInCatchVariables": true,
"skipLibCheck": true, // 加速编译,通常安全
"resolveJsonModule": true,
"isolatedModules": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src"]
}
如果你在做渐进迁移,可先把
strict打开,个别地方用最小范围的// @ts-ignore或as const/类型缩小解围,别把全局规则关掉。
7、类型工具箱:最常用、最容易“值回票价”的几招
// 7.1 基础 & 联合类型
type ID = number | string
// 7.2 可选与精确可选
type User = {
id: ID
name: string
email?: string // exactOptionalPropertyTypes = true 时更精准
}
// 7.3 unknown 优于 any(迫使你做类型收窄)
function parse(json: string): unknown {
return JSON.parse(json)
}
const data = parse("{}")
// data.name // ❌ 不允许直接用
if (typeof data === "object" && data && "name" in data) {
// 经过类型守卫后才能安全使用
}
// 7.4 类型守卫(Type Guards)
function isUser(x: unknown): x is User {
return typeof x === "object" && !!x && "id" in x && "name" in x
}
// 7.5 接口/类型别名与字面量约束
type Role = "admin" | "member"
interface Account {
id: ID
role: Role
}
const rbac = { admin: ["create", "delete"], member: ["read"] } as const
// ^ 字面量推断,后续取值更精确
// 7.6 泛型(保持 API 的“可复用 + 类型安全”)
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const out = {} as Pick<T, K>
for (const k of keys) out[k] = obj[k]
return out
}
8)把“红线”变成“绿线”:典型迁移案例
8.1 属性拼写 & 可空值
type Rect = { width: number; height: number | null }
// ✅ 标注可空,再在使用处做收窄
function area(rect: Rect) {
if (rect.height == null) return 0
return rect.width * rect.height
}
8.2 隐式 any → 显式类型
// ❌ 隐式 any(在 strict 下报错)
function sum(a, b) { return a + b }
// ✅ 显式类型
function sum(a: number, b: number) { return a + b }
8.3 外部数据的“防御性”建模
// 来自接口的 JSON 不可信 -> unknown
const payload: unknown = await fetch(...).then(r => r.json())
if (isUser(payload)) {
// ✅ 通过类型守卫后再用
console.log(payload.name)
}
9、常见坑与最佳实践
- 永远打开
strict:早痛早治,后期收益巨大。 any克制使用:真的没法标注时,用 局部any(比如as any)做“围栏”;更推荐unknown+ 类型守卫。- 先给“边界”加类型:接口响应、持久化数据、公共 API 的入参/出参——优先标注它们。
- 利用 IDE 重构:重命名字段/函数,先让类型系统告诉你哪里会炸。
- 别指望类型改变运行时:记住“类型擦除”的原则——运行时需要的校验(如参数必填、值域范围)仍要代码里做。
- 搭配 ESLint:用
@typescript-eslint补齐风格与潜在逻辑问题(如未用变量、潜在 await 漏洞等)。
10、学习策略:把 JS 资源当成 TS 资源的一部分
TS 的运行时就是 JS。遇到“怎么做 X”类问题,先搜 JavaScript 的做法,通常 100% 适用;需要编译期约束时,再加上 TS 的类型标注或工具类型即可。
- TS 不改变 JS 怎么跑,它只是在运行前为你把错拦下。
- 从“在 JS 里加
// @ts-check+ JSDoc”到“全量 TS”,任何阶段都能获得收益。 - 把类型当成团队契约与系统设计的载体,你会发现:可维护性、重构效率、跨团队协作,都会肉眼可见地提升。
更多推荐


所有评论(0)