前言
做企业级React项目不用TS,后期重构直接崩溃。
很多人只会简单写React.FC,但props泛型、合成事件、useEffect副作用、前端持久化存储全是模糊点。
读完这篇你能掌握:

全文代码可直接复制运行,踩坑点全部标注,新手也能看懂。
一、为什么企业项目强制 React + TypeScript
React本身源码就是TS编写,搭配TS是大型项目标配,核心收益3点:
- 静态类型校验:开发阶段直接报类型错误,不用等到页面运行才崩溃
- 类型即文档:组件props、入参、返回值自带提示,团队协作零沟通成本
- 完善高级语法:泛型、接口、类型别名,解决JS弱类型带来的各种隐性bug
无TS项目痛点:
传参拼写错误、事件对象类型混乱、复杂对象无约束、重构时牵一发而动全身。
二、React函数组件核心类型:React.FC 完整解析
2.1 React.FC 底层定义
// React源码简化定义
type FC<P = {}> = FunctionComponent<P>;
interface FunctionComponent<P = {}> {
(props: P & { children?: ReactNode }): ReactNode;
}
关键点:
2.2 基础Hello组件实战(Props类型约束)
// 用interface定义组件入参规范
interface HelloProps {
name: string;
age?: number; // 可选参数
}
// FC泛型传递Props类型约束
const Hello: React.FC<HelloProps> = ({ name, age }) => {
return (
<div>
<p>你好,{name},今年{age ?? "未知"}岁</p>
</div>
);
};
// 使用
<Hello name="张三" age={22} />
2.3 interface vs type 怎么选(高频踩坑)
| interface | 定义组件props、对象结构、需要扩展属性 |
| type | 联合类型、交叉类型、简单类型别名 |
示例区分:
// interface:支持继承扩展
interface User {
id: number;
name: string;
}
interface UserInfo extends User {
address: string;
}
// type:仅做别名,不支持继承
type UserType = {
id: number;
name: string;
}
三、React合成事件TS类型(表单开发必用)
原生事件写法类型模糊,React提供内置事件泛型React.ChangeEvent,精准绑定DOM元素类型。
3.1 输入框受控组件标准写法
import { useState } from 'react';
const InputDemo: React.FC = () => {
const [editingName, setEditingName] = useState("");
// 泛型指定HTMLInputElement,完整拿到输入框DOM类型
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setEditingName(e.target.value);
};
return (
<input value={editingName} onChange={handleChange} placeholder="请输入名称" />
);
};
踩坑提醒:
不要直接写(e) => void,丢失DOM元素类型,无法获取target完整属性。
3.2 自定义事件父子通信类型规范
子组件抛出自定义事件,父组件接收,统一约束回调函数类型:
// 子组件Props
interface ChildProps {
// 自定义回调,参数为输入字符串
onChangeName: (val: string) => void;
}
const ChildInput: React.FC<ChildProps> = ({ onChangeName }) => {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// 向上抛出值
onChangeName(e.target.value);
};
return <input onChange={handleChange} />;
};
// 父组件使用
const Parent = () => {
const handleUpdate = (name: string) => {
console.log("子组件传过来的值:", name);
};
return <ChildInput onChangeName={handleUpdate} />;
};
四、单向数据流:组件状态分层3个迭代版本(企业标准)
核心原则:父组件持有状态,子组件仅展示,状态通过props下发,修改方法通过自定义事件上抛,也就是UI = fn(props)。
版本1:原始写法(状态分散,可读性差)
子组件私有状态,父子各自维护一份数据,数据不同步,多人协作极易出bug。
// 子组件自己存editingName,父组件无法同步
const Child = () => {
const [editingName, setEditingName] = useState("");
const change = (e: React.ChangeEvent<HTMLInputElement>) => {
setEditingName(e.target.value);
};
return <input value={editingName} onChange={change} />;
};
版本2:折中方案(子私有状态,提交同步父)
子组件编辑时操作本地状态,仅提交时同步父组件,适合临时草稿场景。
版本3:最优方案(状态提升,纯展示子组件)
所有状态统一放到父组件,子组件无任何state,纯受控组件,性能更好、数据统一。
// 父组件:唯一状态持有者
const Parent = () => {
const [editingName, setEditingName] = useState("");
const handleChange = (val: string) => {
setEditingName(val);
};
return <ChildInput value={editingName} onChange={handleChange} />;
};
// 子组件:无状态,只接收props渲染
interface ChildInputProps {
value: string;
onChange: (v: string) => void;
}
const ChildInput: React.FC<ChildInputProps> = ({ value, onChange }) => {
const inputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange(e.target.value);
};
return <input value={value} onChange={inputChange} />;
};
优势:
五、useEffect副作用钩子:替代全套生命周期
5.1 useEffect对应三大生命周期
- 挂载后mounted:依赖数组传[],仅页面初始化执行一次
- 更新后updated:依赖数组填入监听变量,变量变化执行
- 卸载前unmounted:函数内部返回清理函数,组件销毁执行
5.2 接口请求标准实战(最常用场景)
页面挂载后请求后端接口,拿到数据渲染列表:
import { useState, useEffect } from 'react';
interface UserItem {
id: number;
name: string;
}
const UserList: React.FC = () => {
const [userList, setUserList] = useState<UserItem[]>([]);
useEffect(() => {
// 模拟接口请求
const fetchUser = async () => {
const res = await fetch("/api/user");
const data = await res.json();
setUserList(data);
};
fetchUser();
// 卸载清理:取消请求,防止组件销毁后setState报错
return () => {
console.log("组件卸载,清理副作用");
};
}, []); // 空依赖,仅挂载执行一次
return (
<div>
{userList.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
};
踩坑提醒:
依赖数组必须补齐所有内部使用的变量,否则会出现闭包陷阱。
六、前端本地持久化:localStorage vs IndexedDB 完整对比
6.1 localStorage 基础使用代码
限制:仅支持字符串存储,容量约5MB,同步API,阻塞主线程。
// 写入对象必须序列化
const saveUser = (user: UserItem) => {
localStorage.setItem("user", JSON.stringify(user));
};
// 读取需要反序列化
const getUser = (): UserItem | null => {
const str = localStorage.getItem("user");
if (!str) return null;
return JSON.parse(str);
};
// 删除
localStorage.removeItem("user");
// 清空全部
localStorage.clear();
适用场景:少量配置、token、主题、简单偏好设置。
6.2 IndexedDB 核心特点
原生API写法繁琐,项目中统一使用Dexie.js封装简化开发。
6.3 两者核心区别总结
| 读写模式 | 同步,阻塞UI | 异步Promise,不卡顿页面 |
| 存储容量 | 约5MB | 几十MB起,受磁盘配额限制 |
| 数据类型 | 仅字符串,对象需序列化 | 原生支持对象、Blob、ArrayBuffer |
| 查询能力 | 仅按键取值,无筛选 | 支持索引、条件、分页查询 |
| 上手难度 | 极简,一行调用 | 原生复杂,需封装库 |
网硕互联帮助中心



评论前必须登录!
注册