从 JS 转型到 TS 的实战指南:30 分钟上手 TypeScript
引言:为什么我会决定学 TypeScript?

记得两年前,我加入一个中型前端团队,负责一个电商后台系统的重构。当时项目已经运行了两三年,代码库膨胀得很快,模块之间调用混乱、函数参数类型不明、接口返回结构随意更改,导致每次新功能上线都像是“拆弹”——你永远不知道哪一行代码会突然报错。
那时候我们还在使用纯 JavaScript(ES6+),虽然有 ESLint 和 JSDoc 帮忙约束代码质量,但远远不够。后来我们团队做了一个决定:全面引入 TypeScript。
刚开始我也有些抵触,觉得多了不少“冗余”的类型声明,写起来不够灵活。但在第一个迭代周期后,我就彻底改变了看法。TypeScript 给我们带来的不仅仅是类型安全,更是一种开发流程的“安全感”。
今天这篇文章,我想以自己真实的经验为基础,带大家在 30 分钟内快速上手 TypeScript,并结合我在实际项目中遇到的问题,分享一些经验教训和小技巧。
问题描述:JS 项目的“不可控状态”

让我印象深刻的是那次重构订单管理页面的经历。我们原有的一套 API 接口封装,是基于 Axios 编写的几个工具函数,用来请求数据并渲染表格。但问题来了:
- 后端返回的数据结构经常变更
- 函数参数没有清晰定义,有时候传
string,有时候传number - 表格组件内部对字段的处理方式依赖特定结构,一旦变更就会崩溃
这些情况在 JS 中几乎无法提前发现,只能靠手动测试或者上线之后 catch 错误,效率很低。
更严重的是,团队协作时经常出现:“我以为这个字段是 string”,“你怎么不传 id 啊?”。这些都是典型的类型模糊问题。
解决方案:为什么选择 TypeScript?


TypeScript 的核心优势在于 静态类型检查 + 开发提示(IntelliSense)。
我们可以利用它的类型系统提前定义好变量、函数参数、返回值、接口结构等,在编写阶段就能发现潜在问题。更重要的是,它可以在不改变代码逻辑的前提下逐步迁移已有项目,不需要一次性全部重写。
于是我们决定:
- 在新功能模块优先使用
.ts文件 - 已有
.js文件添加// @ts-check来开启简单类型检测 - 使用 VS Code 的智能提示和跳转功能提升开发效率
- 配合 ESLint + Prettier 规范编码风格
入门实践:30分钟掌握 TypeScript 核心概念


以下内容是我根据自己学习路径整理出的最短上手路径,覆盖了绝大多数日常开发中需要用到的核心语法和类型知识。
环境搭建
npm install -g typescript
如果你是在现有项目中使用 TypeScript,建议使用构建工具(如 Webpack、Vite)内置的支持。
Vite 创建项目示例:
npm create vite@latest my-ts-app --template vanilla-ts
创建完成后你会看到 index.html、main.ts 还有关键的配置文件 tsconfig.json。
我的小技巧:VSCode 安装官方插件 “TypeScript Toolkit”,可以自动补全很多类型定义和 import 引入。
第一步:基本类型声明
JavaScript 是动态类型语言,而 TypeScript 则鼓励我们显式地定义类型:
let age: number = 25;
let name: string = "Tom";
let isActive: boolean = true;
function greet(person: string): string {
return `Hello, ${person}`;
}
greet(name); // 正确
greet(age); // 报错!age 不是 string 类型
这样可以避免类似“向 greet 函数传数字”的错误。
第二步:接口(Interface)与类型别名(Type)
当我们要表示对象结构时,可以用 interface 或 type:
interface User {
id: number;
name: string;
email?: string; // 可选属性
}
type Product = {
productId: string;
price: number;
};
注意:
interface支持继承,type更适合联合类型。
比如我们在处理用户信息时,后端可能会返回两种情况:登录用户带有 token,未登录则是公共信息。
interface AuthenticatedUser extends User {
token: string;
}
type UserInfo = User | AuthenticatedUser;
第三步:泛型与数组/函数类型
泛型能让我们写出更通用、复用性更强的代码:
function identity<T>(arg: T): T {
return arg;
}
const output1 = identity<string>("hello"); // 输出为 string
const output2 = identity<number>(42); // 输出为 number
数组的写法有两种:
const numbers: number[] = [1, 2, 3];
const names: Array<string> = ["Alice", "Bob"];
函数类型也可以写成接口:
interface SearchFunc {
(source: string, subString: string): boolean;
}
第四步:可空类型与非空断言
这是我们从 JS 切换到 TS 最容易踩坑的地方之一:
let value: string | null = null;
if (value) {
console.log(value.toUpperCase()); // 运行没问题,TS 也认可
}
console.log(value.toUpperCase()); // ❌ 会报错:Object is possibly 'null'.
解决办法有几种:
- 提前判断是否为空
- 使用非空断言操作符
!(慎用) - 使用默认值或条件赋值
const result = maybeGetResult()!;
不过非空断言很容易埋下隐患,我个人推荐尽量使用可选链操作符:
const title = data?.user?.profile?.title;
第五步:枚举与字面量类型
枚举常用于状态码、按钮类型、选项分类等场景:
enum Status {
Pending,
Approved,
Rejected,
}
function checkStatus(status: Status) {
if (status === Status.Approved) {
// do something
}
}
字面量类型则适用于更细粒度的控制:
type ButtonType = "primary" | "secondary" | "danger";
function renderButton(type: ButtonType) {
// ...
}
第六步:类与装饰器(可选)
如果你的项目是面向对象风格,TS 对类的支持非常好:
class Person {
private name: string;
constructor(name: string) {
this.name = name;
}
sayHi() {
console.log(`Hi, I'm ${this.name}`);
}
}
const p = new Person("John");
p.sayHi();
装饰器(Decorator)常用于 Angular/Vue 等框架中进行元编程,但需要开启实验性支持(见 tsconfig.json 中设置 "experimentalDecorators": true)。
代码实践:订单详情页的重构
回到最初的那个订单管理页面,我们用 TypeScript 来重构一下它的核心部分。
假设后端返回数据格式如下:
{
"orderId": "202308001",
"customer": {
"name": "张伟",
"phone": "139****1234"
},
"products": [
{
"id": "P1001",
"name": "无线蓝牙耳机",
"price": 199.9
}
],
"status": "completed"
}
我们在 types.ts 定义对应的类型:
export type OrderStatus = "pending" | "processing" | "completed" | "cancelled";
export interface OrderProduct {
id: string;
name: string;
price: number;
}
export interface Order {
orderId: string;
customer: {
name: string;
phone: string;
};
products: OrderProduct[];
status: OrderStatus;
}
接着在请求方法里使用:
async function fetchOrder(orderId: string): Promise<Order> {
const res = await fetch(`/api/orders/${orderId}`);
return res.json();
}
这样只要后端接口变了,比如 product 没有 price 字段,我们的 TS 就会在编译时报错,而不是运行时报错!
踩坑经验:那些年我们掉进的“大坑”
坑一:第三方库没类型怎么办?
有时候我们会用一些老库,它们并没有 TypeScript 支持。这时候有两种办法:
- 手动添加类型定义:
declare module 'xxx' - 使用 DefinitelyTyped 社区维护的类型包(安装
@types/xxx)
例如:
npm install --save-dev @types/lodash
如果你不确定某个库有没有类型支持,可以访问 https://definitelytyped.org 查看。
坑二:Vue 项目怎么配置?
如果是 Vue 2 项目,可以通过 vue-property-decorator 结合 TS 使用。而 Vue 3 + Vite 则原生支持 <script lang="ts">:
<script setup lang="ts">
import { ref } from 'vue'
const count = ref<number>(0)
</script>
我们当时从 Vue 2 升级到 Vue 3 的过程也顺带完成了 TypeScript 化,效果非常好。
坑三:类型太复杂,推导不出来?
有时复杂的嵌套结构或者泛型嵌套太多会让 TS 无法推导出正确类型,这时候需要显式标注:
function getFirstItem<T>(arr: T[]): T {
return arr[0]
}
const items = [1, 2, 3];
const first = getFirstItem<number>(items);
不要迷信“类型推导”,尤其是复杂场景下,显式标注反而更清晰。
效果总结:TypeScript 带来的收益
自从我们项目接入 TypeScript 以后,收获非常明显:
- 团队协作变得更加顺畅,沟通成本降低
- 新人上手更快,因为代码结构更清晰
- 上线后错误明显减少,尤其是 API 相关的类型错误
- IDE 提示更好用了,写代码体验大幅提升
而且我们还结合 CI 自动加上了 tsc --noEmit --watch,每次提交代码前都会做一次类型检查,确保基础问题不会流入生产环境。
经验分享:给新手的几点建议
✅ 1. 从 JS 开始渐进式迁移
TypeScript 并不是必须全量使用的,你可以先在 JS 文件顶部加 // @ts-check,然后逐步转换为 .ts 文件。
✅ 2. 善用 VS Code 插件
除了基础的 IntelliSense,我还推荐两个插件:
- TypeScript Importer:自动根据路径和文件名补全导入语句
- Path Intellisense:补全相对路径更高效
✅ 3. 多写类型注解,少用 any(尤其不能滥用 unknown)
很多人为了省事写 any 类型,这样就失去了 TS 的意义。建议用 unknown 替代,强制你在使用前做类型收窄。
✅ 4. 合理使用联合类型和类型守卫
比如我们有个组件接受不同的数据来源:
type Source = LocalDataSource | RemoteDataSource;
function fetchData(source: Source) {
if ('localData' in source) {
// 处理本地数据
} else {
// 请求远程接口
}
}
✅ 5. 别怕“啰嗦”,多写类型让代码更易读
虽然初学时会觉得要写很多额外的类型声明,但这其实是投资未来的行为。长远来看,这种“啰嗦”会让你的代码更健壮。
写在最后:TypeScript 让我重新爱上写代码
说实话,刚接触 TypeScript 的时候我也觉得麻烦。但慢慢地我发现,它不仅帮我抓住了很多隐藏的 bug,更提升了我对代码质量的关注。
尤其是在大型项目中,有了类型系统的加持,你可以放心大胆地重构、扩展,而不用担心一个小小的改动就引发蝴蝶效应。
如果你现在正在犹豫要不要学 TypeScript,那我的建议是:越早越好。它已经成为现代前端工程化不可或缺的一部分。
希望这篇来自真实项目经验的文章,能帮你快速入门,并找到属于自己的编码节奏。
祝你 coding 愉快,少踩坑、多成就 😊
如有问题欢迎留言交流,我们一起成长 💬

评论 0