TypeScript 快入门前夜:一次从 JavaScript 到 TypeScript 的实战探索

Nginx门卫
2025-06-29 13:14
阅读 522

开篇:为什么我决定用 TypeScript 写项目?

开篇:为什么我决定用 TypeScript 写项目?

去年年底,我们团队接手了一个相对庞大的内部管理系统重构任务。之前的前端代码完全基于 JavaScript 编写,随着功能迭代和人员更替,项目的可维护性越来越差,尤其是在协作开发中,由于变量类型不明确、函数签名不清晰等原因,频繁出现“你传的参数到底是什么类型?”、“这个字段可能不存在?为什么没报错?”等沟通成本极高的问题。

在一次技术选型讨论会上,我提出了一个想法:“如果我们尝试用 TypeScript 来重构部分模块呢?”起初,大家有些迟疑:毕竟团队成员大多数是 JavaScript 老手,对 TypeScript 了解不深,担心学习曲线陡峭、初期效率下降。但我坚信,长远来看,静态类型带来的好处远大于短期的适应期。

于是,我们决定从小模块开始试点,逐步向 TypeScript 迁移,并在这个过程中总结出了一套适合快速入门和实际落地的经验,也就是今天想分享给大家的内容——《TypeScript 快速入门:30 分钟上手指南》

这篇文章不是一篇教程式文档,而是结合我在项目中遇到的真实场景,告诉你如何从零开始高效地使用 TypeScript,并从中获得实实在在的好处。


问题描述:JavaScript 维护成本高得让人头疼

CSS动画效果展示-1

问题描述:JavaScript 维护成本高得让人头疼

我们面对的问题其实非常典型:

  • 类型不确定性:很多接口返回的数据结构没有统一规范,函数接收的参数类型也不固定,导致运行时错误频发。
  • 协作困难:多人协作开发中,常常因为某个函数的设计意图不明而导致调用错误。
  • 重构风险大:一旦修改某个核心对象的结构,很难保证不会影响到其它模块。
  • 缺乏自动提示与检查:IDE 对 JavaScript 的智能提示有限,尤其在大型对象或复杂嵌套结构下,容易打错字段名却无法立即发现。

举个例子,在登录逻辑中有一个 fetchUserInfo 函数,它原本的实现如下:

function fetchUserInfo(id) {
  // 假设这里是调用后端接口获取用户信息
  return {
    id: id,
    name: 'Tom',
    email: 'tom@example.com',
    roles: ['admin', 'developer']
  };
}

某天,另一个同事想复用这个函数处理一个新需求,但传入的是一个数字字符串,结果程序报错,因为他预期 id 是一个整数。而由于 JavaScript 的动态特性,这个错误在编译阶段根本无法被发现,只能等到运行时才能暴露。

这种“隐式”类型的陷阱在整个项目中比比皆是。我们需要一个机制来提前发现问题,避免这类低级但成本巨大的错误。


解决方案:TypeScript 登场,类型为王

解决方案:TypeScript 登场,类型为王

TypeScript 最大的价值就在于它提供了类型系统 + 兼容 JavaScript 的语法。你可以选择渐进式引入,也可以直接新建 TS 文件进行开发,无需全量重写。这给了我们非常灵活的迁移空间。

第一步:安装 & 配置环境

我们的项目是一个 Vue + Vite 构建的前端应用,添加 TypeScript 非常简单:

npm install --save-dev typescript tsup @vitejs/plugin-vue-ts

然后创建 tsconfig.json(可以使用 tsc --init 自动生成):

{
  "compilerOptions": {
    "target": "es2015",
    "module": "esnext",
    "strict": true,
    "jsxFactory": "h",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"]
}

这里最重要的是开启了 strict 模式,它包括了最严格的类型检查规则,比如不允许隐式 any 类型、不允许未赋值变量等。刚开始可能会有点严,但这是写出安全代码的关键。

接着,在 Vite 配置中添加插件支持 .ts.vue.ts 文件:

import vue from '@vitejs/plugin-vue'
import vueTs from '@vitejs/plugin-vue-ts'

export default defineConfig({
  plugins: [
    vue(),
    vueTs()
  ]
})

配置完成后,就可以愉快地写 .ts 文件了!


第二步:从一个函数入手,体验类型的力量

回到那个 fetchUserInfo 函数,我们可以这样改写:

interface UserInfo {
  id: number;
  name: string;
  email?: string; // 可选属性
  roles: string[];
}

function fetchUserInfo(id: number): UserInfo {
  return {
    id: id,
    name: 'Tom',
    // email: 'tom@example.com',
    roles: ['admin', 'developer']
  };
}

注意几个关键点:

  • 我们用 interface 定义了一个数据结构 UserInfo,并将其作为函数的返回类型。
  • 参数 id 加上了类型注解为 number,如果传入字符串就会在编译阶段报错。
  • email 字段是可选的(加上 ?),说明它可以存在,也可以不存在。

现在如果我们误把 id 当成字符串传进来,编辑器会立刻提示:

const user = fetchUserInfo("1"); // ❌ 类型“string”不能赋给类型“number”

再也不用等到运行时报错了。


第三步:让现有 JS 代码也能享受类型安全

我们不可能一夜之间把所有 JS 文件都重写为 TS,那怎么办呢?

答案很简单:使用 .d.ts 类型定义文件 + 启动 allowJscheckJs

我们只需要:

  1. tsconfig.json 中开启以下配置:
{
  "compilerOptions": {
    ...
    "allowJs": true,
    "checkJs": true
  }
}
  1. 在对应的 .js 文件旁边创建 .d.ts 文件,为其声明类型。

例如,原来的 utils.js

export function formatTime(time) {
  return new Date(time).toLocaleString();
}

我们可以为其编写 utils.d.ts

export function formatTime(time: Date | string): string;

这样 IDE 就能正确识别这个函数的参数类型,并给出提示。你不需要马上将 JS 改成 TS,就能享受到类型系统的红利。


第四步:类型推导 vs 显式类型声明 —— 如何取舍?

TypeScript 提供了强大的类型推导能力,很多时候你不需要手动声明类型,TS 自己就能推断出来。

比如:

let message = "Hello, TypeScript!";
// 不需要声明 string 类型,TS 已经知道

但在某些情况下,显式声明仍然非常必要,尤其是涉及到复杂的联合类型、对象嵌套、泛型等情况。

我的建议是:

  • 简单变量尽量交给类型推导(提升开发效率)
  • 接口返回结构、函数入参和返回值最好显式声明(增强可读性和可维护性)

第五步:实战案例:重构表单验证逻辑

这是一个真实项目中遇到的痛点。原有一段用于校验表单填写是否完整的代码:

function validateForm(formData) {
  if (!formData.name) return false;
  if (!formData.email) return false;
  if (!formData.agree) return false;

  return true;
}

这段代码看似简单,但在开发中经常因为 formData 结构变动或者某些字段为空值而出现问题。更糟的是,其他同事使用这个函数时,根本不知道它到底依赖哪些字段。

我们把它用 TypeScript 改写如下:

interface FormFields {
  name: string;
  email: string;
  agree: boolean;
}

function validateForm(formData: FormFields): boolean {
  if (!formData.name) return false;
  if (!formData.email) return false;
  if (!formData.agree) return false;

  return true;
}

现在,任何人调用这个函数时,编辑器都会提示他必须传入哪些字段,而且字段类型也受到约束。比如下面这种情况:

validateForm({ name: 'Alice' }); // ❌ 缺少 email 和 agree 字段

这就是类型安全的魅力。


第六步:泛型的应用 —— 让函数更通用

我们还遇到了这样一个需求:封装一个根据 key 返回对象中对应 value 的函数。

JavaScript 版本大概是这样的:

function get(obj, key) {
  return obj[key];
}

但在 TypeScript 中,我们可以利用泛型让它更健壮:

function get<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

解释一下:

  • T 是对象类型
  • K 是对象中的键名,且必须是 T 的 key
  • 返回值就是 T[K],即该 key 对应的值的类型

这意味着,当你尝试访问一个不存在于对象中的属性时,TypeScript 会在编译期报错:

const user = { name: 'Jerry' };
get(user, 'age'); // ❌ 类型“"age"”的参数不能赋给类型“"name"”的参数

这才是真正的防患于未然。


效果总结:TypeScript 带来了什么?

效果总结:TypeScript 带来了什么?

经过这次实战探索,我们收获了以下几点明显的好处:

  • 减少运行时错误:很多原本难以察觉的错误在编码阶段就被拦截。
  • 提升代码可读性和可维护性:清晰的类型定义让后续开发更容易理解函数意图。
  • 增强 IDE 智能提示能力:无论是字段补全还是函数参数提示,效率大大提升。
  • 提升协作效率:接口定义清晰后,前后端之间的联调变得更加顺畅。
  • 加快后期重构速度:有了类型系统加持,修改结构时可以放心大胆地改动,不用担心连带影响。

更重要的是,整个迁移过程并没有我们最初担心的“效率下降”,反而因为类型系统帮助我们更早发现问题,整体进度更快了。


经验分享:给初学者的几点建议

如果你刚刚接触 TypeScript,或者正准备在项目中引入,这里是我踩坑之后总结的一些建议:

1. 先看懂再写

不要急于给自己设定太高目标,建议先阅读官方文档或社区开源项目的代码,看看别人怎么用 interface、type、泛型等特性。

推荐资源:

2. 从函数和组件开始写起

不需要一开始就写大型模块,试着从一个工具函数、一个 UI 组件开始,边写边学,感受类型带来的优势。

3. 合理使用联合类型和可选属性

避免滥用 any 或者 unknown。这两个虽然能解决类型问题,但同时也绕过了类型检查,属于“不得已而为之”的兜底方案。

多使用联合类型:

type Status = 'pending' | 'success' | 'failed';

或者可选属性:

interface User {
  id: number;
  name: string;
  email?: string;
}

这样既能保持灵活性,又能保证类型安全。

4. 开启 strict 模式,逼自己养成好习惯

一开始可能会觉得严格模式太苛刻,但这恰恰是最能锻炼你的地方。你会发现,写出来的代码更严谨,逻辑也更清晰。

5. 调试时善用类型提示

Chrome DevTools 有时也会显示变量类型,不过 VS Code + Volar 插件会让你更轻松看到类型结构、报错原因、建议修复方式。


结尾:类型是代码的另一种文档

写到这里,我想说的是,TypeScript 并不只是类型标注,它是对代码逻辑的一种结构化表达。它让你写的不仅仅是能跑的代码,更是能让别人看懂、能被机器理解、能持续演进的代码。

回想当初我们还在争论是否要迁移到 TS 时,我也一度犹豫过:“加那么多类型,会不会限制开发自由度?” 但现在回头看看,正是这些“限制”,才带来了更高的质量保障和更强的工程化能力

所以,无论你是刚入行的小白,还是已经写了多年 JavaScript 的老手,我都诚恳地建议你抽出一点时间来学一学 TypeScript。不需要三天学会,甚至不需要一口气读完一本书。只要你愿意动手实践,哪怕每天花 30 分钟写一个小函数,你也会很快感受到它的魅力。

最后送一句话共勉:

“代码是用来给人读的,偶尔也给机器跑跑。”
—— 引自《计算机程序的构造和解释》
(SICP)

让我们一起写出既有人看懂、也有机器理解的高质量代码吧!

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝