TypeScript 快手指南:从入门到小项目实战,30分钟上手
作为一名前端开发者,在这5年的工作中,我经历了从jQuery时代到React、Vue再到如今的框架百花齐放,而在这之中最让我感到“如鱼得水”的,就是 TypeScript。
记得在2019年的时候,我们团队第一次尝试引入TypeScript。当时我还在一个电商系统的重构项目中担任主力开发,项目结构已经变得臃肿不堪,JavaScript中大量any类型变量和随意的对象结构让维护成本极高。那会儿,我和同事常常一边debug一边抓头发:这个函数返回的到底是个字符串还是个对象?为什么传过来的数据结构不一致?
是时候来点更靠谱的东西了,我们决定开始转型TypeScript。
从痛苦出发,为何选择TypeScript?


那个项目原本是纯JS写的,页面交互逻辑复杂,接口调用频繁。随着业务迭代,团队成员越来越多,代码风格也开始千奇百怪:
- 函数参数无校验,有时候传的是id,结果你发现有人传了整个对象;
- 返回值类型不确定,处理时经常需要额外判断;
- 调试困难,尤其是多人协作下,很难快速定位错误源头。
这些问题累积下来,导致新功能上线前的回归测试时间越来越长,线上也频频报错。
为了改善这一局面,我们在新一轮迭代中,决定使用TypeScript作为主要开发语言,同时采用渐进式迁移策略——从核心模块开始入手,逐步将关键组件转为TS。
初次接触TypeScript的感受

刚开始学TypeScript的时候,我也踩了不少坑。最大的障碍不是语法本身,而是思维方式的转变:以前写JS可以随心所欲,现在每个变量都得加上类型,稍有不慎就报错。
比如我当时写了一个简单的表单提交方法:
function handleSubmit(formData: any) {
api.post('/submit', formData).then(res => {
console.log(res);
});
}
看起来没问题吧?但后来我要做验证逻辑的时候,发现根本不知道formData里面有什么字段。于是我把any改成了一个接口:
interface FormData {
name: string;
email: string;
phone?: string; // 可选字段
}
function handleSubmit(formData: FormData) {
// ...
}
这下好了,编译器会帮我检查是否漏掉了必填字段,甚至在IDE里还能自动补全。
小项目实战:写一个简单的待办事项应用

与其空谈理论,不如直接来个实操。下面我带大家一起写一个简单的todo应用,用TypeScript + React实现(也可以换成Vue或其他框架)。
项目背景
我们的目标是构建一个具备以下功能的小型应用:
- 输入框输入待办事项内容
- 点击添加后列表展示新增项
- 每一项可标记为完成状态
- 完成事项支持删除操作
技术选型
- 框架:React(create-react-app生成)
- 语言:TypeScript
- 样式:CSS Modules
- 状态管理:useState + useReducer(简单场景不需要Redux)
初始化项目
先通过create-react-app创建一个TS模板:
npx create-react-app todo-ts --template typescript
cd todo-ts
npm start
项目跑起来后,你会看到经典的计数器页面。
编写类型定义
首先定义Todo的数据结构:
// types/todo.ts
export interface Todo {
id: number;
text: string;
completed: boolean;
}
再定义操作类型Action:
// types/actions.ts
import { Todo } from './todo';
export enum TodoActionTypes {
ADD = 'ADD_TODO',
TOGGLE = 'TOGGLE_TODO',
REMOVE = 'REMOVE_TODO',
}
export type TodoAction =
| { type: TodoActionTypes.ADD; payload: { text: string } }
| { type: TodoActionTypes.TOGGLE; payload: { id: number } }
| { type: TodoActionTypes.REMOVE; payload: { id: number } };
编写reducer管理状态
接下来使用useReducer统一管理todo的状态变化:
// reducer/todoReducer.ts
import { Todo, TodoAction, TodoActionTypes } from '../types';
const initialTodos: Todo[] = [];
export function todoReducer(state: Todo[] = initialTodos, action: TodoAction): Todo[] {
switch (action.type) {
case TodoActionTypes.ADD:
return [
...state,
{
id: Date.now(),
text: action.payload.text,
completed: false,
},
];
case TodoActionTypes.TOGGLE:
return state.map(todo =>
todo.id === action.payload.id ? { ...todo, completed: !todo.completed } : todo
);
case TodoActionTypes.REMOVE:
return state.filter(todo => todo.id !== action.payload.id);
default:
return state;
}
}
主体组件编写
最后是App组件:
// App.tsx
import React, { useState, useReducer } from 'react';
import { todoReducer } from './reducer/todoReducer';
import { Todo, TodoAction, TodoActionTypes } from './types';
import './App.css';
function App() {
const [inputValue, setInputValue] = useState('');
const [todos, dispatch] = useReducer(todoReducer, []);
const handleAdd = () => {
if (!inputValue.trim()) return;
dispatch({
type: TodoActionTypes.ADD,
payload: {
text: inputValue.trim(),
},
});
setInputValue('');
};
const handleToggle = (id: number) => {
dispatch({
type: TodoActionTypes.TOGGLE,
payload: {
id,
},
});
};
const handleRemove = (id: number) => {
dispatch({
type: TodoActionTypes.REMOVE,
payload: {
id,
},
});
};
return (
<div className="App">
<h2>TypeScript Todo Demo</h2>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Enter new todo..."
/>
<button onClick={handleAdd}>Add</button>
<ul>
{todos.map((todo) => (
<li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : '' }}>
<span onClick={() => handleToggle(todo.id)}>{todo.text}</span>
<button onClick={() => handleRemove(todo.id)}>X</button>
</li>
))}
</ul>
</div>
);
}
export default App;
这样我们就完成了一个完整的todo应用啦!
踩过的坑和解决办法
在实际使用过程中,我也遇到过不少问题,分享几个比较典型的。
坑一:any泛滥问题
刚上TS的时候,很多地方不会写类型,就直接any糊过去。后来查代码时发现一堆any,失去类型检查的意义了。
解决方案:逐步替换。可以先使用unknown替代any,强制自己显式类型检查。
function parseInput(input: unknown) {
if (typeof input === 'string') {
// OK
} else {
// 错误提示
}
}
坑二:第三方库没有类型定义
有些旧库在NPM上找不到.d.ts文件,导致TS抱怨。
解决方案:安装对应type包,或者手写类型声明文件,放入src/types/index.d.ts中全局可用。
例如:
declare module 'some-awesome-lib' {
export default function doSomething(): void;
}
坑三:过度类型推断导致无法扩展
有时候为了偷懒,写了过于具体的类型,后期想扩展却发现各种报错。
经验教训:尽量使用组合类型,而非具体枚举或联合类型,除非必须限制。
上线后的效果总结
这个todo只是一个例子,但在真实项目中,我们迁移完成后,明显感受到以下几个好处:
- 错误提前暴露:编译阶段就能发现问题,减少运行时bug
- 代码可读性提升:新人更容易理解数据结构和函数行为
- 重构更有信心:改函数签名时,IDE能自动提示所有引用位置
- 性能影响几乎可以忽略:TS只是开发阶段使用的工具,最终编译成普通JS运行,浏览器兼容性和性能不受影响
给新手几点建议
如果你刚接触TypeScript,别急着全量重构。可以从这几个点下手:
- 先写类型,再写逻辑:养成好习惯,把类型想清楚再动键盘
- 利用好JSDoc+TS能力:VSCode里Ctrl+鼠标悬停可以查看详细类型信息
- 善用联合类型和条件类型:避免冗余的if/else判断
- 多看官方文档和社区最佳实践:https://www.typescriptlang.org/docs 是宝藏网站
- 不要一开始追求完美类型:先把大方向搞对,细节可以慢慢打磨
后记:TypeScript带来的不仅是类型安全

TypeScript带给我的不仅仅是“类型安全”,更重要的是一种思维方式上的改变:从随意地“写着玩”变成认真思考每一个变量的边界和用途。
在这个代码即文档的时代,TypeScript已经成为大型前端项目的标配。它就像是一套自带注释的语言,让人与人之间的合作更高效,也让项目走得更远。
如果你还没开始TypeScript之路,真的强烈推荐你尽快迈出第一步。你会发现,写起代码来不仅更稳,心态也会更轻松——因为你知道,TS已经在帮你兜底了。

评论 0