从"运行时报错"到"写代码就报错",我只差了一个 TypeScript
大家好,我是那个曾经在凌晨三点被 Cannot read property 'name' of undefined 气哭的前端仔。
直到我遇见了 TypeScript ------ 一个让 JavaScript 从"自由奔放的野马"变成"纪律严明的特种兵"的神奇存在。
今天,就结合我最近写的几个小 Demo(没错,就是你看到的那些 .tsx 文件),带大家轻松入门 TypeScript,并聊聊它到底凭什么成为 React 大型项目的"标配"。
🤔 JavaScript:自由,但有点"裸奔"
先说说我们的老朋友 JavaScript。
它灵活、动态、写起来飞快------
ini
let user = { name: "小芳" };
console.log(user.age.toFixed(2)); // 💥 运行时炸了!
问题在哪?
user 根本没有 age 属性!
但 JS 不管,它只会在你点击按钮、提交表单、上线后第 3 分钟才冷冷地告诉你:"兄弟,你错了。"
这就是 弱类型 + 动态语言 的代价:自由的代价是不确定性。
🛡️ TypeScript:给代码穿上"类型盔甲"
而 TypeScript(TS) 是 JavaScript 的超集(Superset),由微软开发。
简单说:所有合法的 JS 代码都是合法的 TS 代码,但 TS 多了一层"类型系统"。
看我 App1.tsx 里的这段代码:
typescript
interface User {
name: string;
age: number;
isSingle?: boolean; // 可选属性
}
const user: User = {
name: "小芳",
age: 18,
isSingle: true
};
这里我用 interface 明确告诉 TypeScript:
"嘿,
User这个对象必须有name(字符串)和age(数字),isSingle可有可无。"
如果我手滑写成:
arduino
const user: User = {
name: "小芳",
age: "十八" // ❌ 类型错误!
};
VS Code 马上红波浪线警告 ,甚至根本编译不过!
不用等到用户点击按钮,在你敲完这行代码的瞬间,TS 就已经把你按在地上教育了。
这就是 TS 的核心哲学:把运行时错误,提前到开发时消灭。
🧩 TypeScript 的"独门绝技"
1. 静态类型检查:代码还没跑,bug 已阵亡
比如我在 App1.tsx 里定义:
ini
let count: number = 10;
const title: string = "Hello ts";
const list: number[] = [1, 2, 3];
如果我试图:
python
count = "十"; // ❌ Error! Type 'string' is not assignable to type 'number'
TS 直接拒绝。再也不用担心"数字变字符串"这种低级错误。
2. 元组(Tuple):固定长度 + 固定类型的数组
typescript
const tuple: [string, number] = ["释永乐", 18];
这个变量第一个元素必须是字符串,第二个必须是数字 ,多一个少一个都不行。
适合表示像 [姓名, 年龄]、[状态码, 消息] 这种结构化数据。
3. 枚举(Enum):让魔法数字/字符串有意义
arduino
enum Status {
Pending, // 0
Fullfilled, // 1
Rejected // 2
}
const pStatus: Status = Status.Pending;
比起用 0、1、2 表示状态,Status.Pending 一眼就知道含义,可读性拉满!
⚠️ 小插曲:如果你用了 Vite + SWC,默认不支持普通
enum,建议改用as const对象(文末彩蛋会讲)。
4. 接口(Interface):组件通信的"契约"
看我的 HelloComponent.tsx:
typescript
interface Props {
name: string;
}
const HelloComponent: React.FC<Props> = (props) => {
return <h2>Hello user: {props.name}</h2>;
};
这里 Props 就是一份合同:
"谁想用我这个组件,就必须传一个
name字符串进来!"
如果父组件忘了传,或者传了个数字:
ini
<HelloComponent name={123} /> // ❌ 类型错误!
TS 立刻报警。组件间的协作从此不再靠"口头约定" ,而是靠"法律条文"(类型定义)。
5. 泛型 + React 事件:精准控制每一处细节
再看 NameEditComponent.tsx:
typescript
interface Props {
userName: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
这里 React.ChangeEvent<HTMLInputElement> 精确指定了:
- 这是一个 输入框的 change 事件
e.target.value一定是string类型
再也不用写 e.target?.value as string 这种"类型断言求生术"了!
🎯 为什么 React 项目爱死 TypeScript?
因为 React 本身就是组件化 + 数据驱动的典范,而 TS 能完美约束:
- 组件 props 的结构
- state 的类型
- 事件处理函数的签名
- API 返回的数据格式
比如我在 App.tsx 中:
csharp
const [name, setName] = useState<string>("initialName");
const setUsernameState = (event: React.ChangeEvent<HTMLInputElement>) => {
setName(event.target.value); // ✅ value 已知是 string
};
useState<string> 明确告诉 TS:这个 state 只能是字符串。
配合 NameEditComponent 的 userName={name},整个数据流从源头到终点都被类型保护着。
这就是所谓的 "单向数据流 + 类型安全" ,大型项目维护起来如丝般顺滑。
😂 一个真实场景对比
| 场景 | JavaScript | TypeScript |
|---|---|---|
忘记传 name prop |
页面显示 Hello user: undefined,用户懵逼 |
编译失败,开发者当场社死(但 bug 没上线) |
| 把数字当字符串拼接 | "10" + 5 = "105",逻辑错乱 |
类型错误,根本过不了编译 |
| API 返回结构变了 | 运行时报错,用户投诉 | 接口定义不匹配,开发阶段就发现 |
TS 不是让你少写 bug,而是让你根本没机会把 bug 写进代码里。
🎁 彩蛋:现代 TS 最佳实践(避开坑)
虽然 enum 很香,但在 Vite + SWC 项目中可能不兼容。
更推荐使用 as const 对象:
rust
const Status = {
Pending: 'pending',
Fulfilled: 'fulfilled',
Rejected: 'rejected'
} as const;
type Status = typeof Status[keyof typeof Status]; // 'pending' | 'fulfilled' | 'rejected'
✅ 兼容所有工具链
✅ 类型安全
✅ 运行时可用
✅ 支持 tree-shaking
🌟 结语:TypeScript,是约束,更是自由
很多人觉得 TS "啰嗦"、"要写很多类型注解"。
但当你经历过一次 "因为一个 typo 导致线上支付失败" 的事故后,
你会明白:这些"啰嗦",其实是对未来的自己最大的温柔。
TypeScript 不是限制你的创造力,而是帮你把精力集中在业务逻辑上,而不是调试 undefined 上。
所以,别再裸写 JavaScript 了!
给你的代码穿上 TypeScript 的"防弹衣",
让它在 bug 的枪林弹雨中,依然坚挺如初 💪。
喜欢这篇文章?欢迎点赞、收藏、评论!
你还在用纯 JS 吗?来聊聊你被类型坑过的经历吧~ 😄