5.2 项目开发实战


文档摘要

5.2 项目开发实战 本节摘要:本章是整套教程的收尾演练——把前四章的工具和第五章的架构,组装成一个能上线的真实项目。以一个「任务管理应用」为例子,走完整条链路:需求拆解、技术选型、项目搭建、组件规划、数据与状态、编码规范、测试策略、性能优化与部署。每一步都有可执行的做法,让你看完就能照着做。 本节导读 阅读完本节,你应当能够: 把一个模糊需求拆解成功能模块与页面清单 依据项目约束完成技术选型 从零搭建项目并规划组件与目录 实现数据获取、状态管理、表单与路由 配置代码规范、测试与部署,交付可上线应用 一、问题与直觉:从「会写组件」到「交付一个项目」 前面所有章节都在教「能力」,本节把它们串成「流程」。很多人学完 React 每个 API 都懂,但真被丢进一个项目时不知道怎么开始。

5.2 项目开发实战

本节摘要:本章是整套教程的收尾演练——把前四章的工具和第五章的架构,组装成一个能上线的真实项目。以一个「任务管理应用」为例子,走完整条链路:需求拆解、技术选型、项目搭建、组件规划、数据与状态、编码规范、测试策略、性能优化与部署。每一步都有可执行的做法,让你看完就能照着做。

本节导读

阅读完本节,你应当能够:

  1. 把一个模糊需求拆解成功能模块与页面清单
  2. 依据项目约束完成技术选型
  3. 从零搭建项目并规划组件与目录
  4. 实现数据获取、状态管理、表单与路由
  5. 配置代码规范、测试与部署,交付可上线应用

一、问题与直觉:从「会写组件」到「交付一个项目」

前面所有章节都在教「能力」,本节把它们串成「流程」。很多人学完 React 每个 API 都懂,但真被丢进一个项目时不知道怎么开始。缺的不是知识,是「从需求到上线的操作顺序」。

本节用一个任务管理应用(Todo 应用的工程化版本)走完整条链路。它麻雀虽小但五脏俱全——有列表、有表单、有筛选、有路由、有状态、有接口调用,几乎覆盖了前四章的所有知识点。

二、第一步:需求拆解

拿到一个模糊需求「做一个任务管理工具」,第一件事不是写代码,是拆需求。把「模糊的一句话」拆成「明确的功能点」:

用户视角的功能清单

  1. 注册登录,登录后看到自己的任务
  2. 任务列表:展示、按状态筛选(全部/未完成/已完成)
  3. 新建任务:填标题、描述、截止日期
  4. 编辑任务:改状态(完成/未完成)、改内容
  5. 删除任务:带二次确认
  6. 数据持久化:刷新后任务还在

先不纠结每个功能的技术实现——「筛选」可能用 state 也可能用路由参数,那是实现细节,需求层面只要确定「用户能按状态筛选」这个事实。

拆成页面与模块

页面/模块 核心功能
登录页 登录、注册切换
任务列表页 列表、筛选、分页(如需要)
任务编辑 新建、编辑共用的表单
布局 顶部导航、主内容区

需求拆解的原则:先列「用户能做什么」,再定「界面有什么」。功能点来自用户视角,页面结构从功能点推出来。别一上来就想「我要用什么组件库、什么状态库」——选型是第二步的事。

需求拆解的产出物

拆完需求,产出三样东西,它们直接指导后续开发:

用户故事清单。每条一句话:「作为用户,我想……以便……」这种格式把「为什么做」也记下来——需求变动时能追溯到动机。

页面路由表。每个页面对应一个 URL:/login、/tasks、/tasks/:id。路由表定了,页面结构就有了骨架。

数据模型草稿。任务对象长什么样:id、title、description、dueDate、done、createdAt。数据模型草稿让前后端联调有共同语言,也让状态设计有依据。

这三样产物加起来不到一页纸,却能把「模糊需求」变成「可执行方案」。跳过它们直接写代码,写一半才发现理解偏了的成本,比写这三样高得多。需求阶段多花十分钟,实现阶段省下的是几小时的返工。

三、第二步:技术选型

按第 3、4 章的选型框架,为这个项目定工具链:

领域 选择 理由
脚手架 Vite 冷启动快、配置透明
语言 JavaScript 项目规模小,暂不需要 TS(规模大再上)
路由 React Router 有登录页/列表页,多页面需要
状态 Context + useState 登录态全局低频,Context 够用
数据请求 fetch 封装 接口少,不需要引 axios/请求库
UI 组件 手写基础组件 组件需求简单,引库反而重
样式 CSS Modules 团队熟悉传统 CSS,组件少
测试 Vitest + Testing Library Vite 生态,API 兼容 Jest

注意选型逻辑:每个选择都是「够用就好」。这个项目的复杂度撑不起 Redux + antd + axios 全家桶,引了是负担。选型跟着需求走,不是跟着「主流」走——这套逻辑在前四章反复出现,这里是最完整的落地。

四、第三步:项目搭建与目录规划

按 5.1 节的目录思路建项目:

src/ api/ # 请求层 tasks.js auth.js components/ # 通用组件 Button/ TaskItem/ Modal/ features/ # 业务模块 auth/ LoginPage.jsx tasks/ TaskListPage.jsx TaskForm.jsx useTasks.js store/ # 全局状态(登录态) authContext.jsx utils/ # 工具 date.js styles/ global.css App.jsx main.jsx

这个目录严格对应 5.1 节的架构:api 是数据层,useTasks 是逻辑层,features/components 是 UI 层。目录是架构的物理投影——架构怎么想,目录就怎么摆,两者保持一致,团队才不会迷路。

搭建的实操顺序:Vite 建项目 → 装依赖(react-router-dom)→ 建目录 → 配路径别名与代理 → 写全局样式 → 写路由骨架。先把「骨架」立起来,再往里面填内容。

请求层的封装

数据请求统一走封装的请求层(第 4.5 节的模式),业务代码不碰 fetch 细节:

// api/tasks.js(示意) const request = async (url, options = {}) => { const res = await fetch(`/api${url}`, { headers: { 'Content-Type': 'application/json' }, ...options, }); if (!res.ok) throw new Error(`请求失败:${res.status}`); return res.json(); }; export const fetchTasks = () => request('/tasks'); export const createTask = (data) => request('/tasks', { method: 'POST', body: JSON.stringify(data), }); export const updateTask = (id, data) => request(`/tasks/${id}`, { method: 'PUT', body: JSON.stringify(data), }); export const deleteTask = (id) => request(`/tasks/${id}`, { method: 'DELETE' });

请求层把「URL、方法、错误处理」收进一个文件。接口一变,只改这里;加统一错误处理,也改这里。第 4.5 节讲 axios 拦截器时说过同样的话——封装的意义是「业务代码不依赖请求细节」。

状态设计的落地

第 5.1 节的状态分配原则在这个项目里落地:

  • 登录态:全局 Context,低频,登录/退出时变
  • 任务列表:页面级 Hook(useTasks),路由进入时加载
  • 表单状态:表单组件内部 useState
  • 筛选状态:列表页内部 useState

每个状态都有明确归属,没有「谁都能改」的全局大对象。任务列表不做成全局状态是有意的——它只在列表页用,放全局只会让其他页面无谓重渲染。这个「状态跟着使用范围走」的判断,是 5.1 节状态分配原则的日常化。

五、第四步:核心实现

登录与路由守卫

登录态用 Context 管理(第 2.5 节模板),未登录访问受保护页重定向:

function RequireAuth({ children }) { const { user } = useAuth(); if (!user) return <Navigate to="/login" replace />; return children; } // 路由配置 <Routes> <Route path="/login" element={<LoginPage />} /> <Route path="/" element={ <RequireAuth> <TaskListPage /> </RequireAuth> } /> </Routes>

登录页处理「登录成功跳转、失败回显错误」两件事——成功 navigate('/'),失败把服务端错误显示在表单下方。第 3.4 节的服务端错误回显模式在这里直接复用。

任务列表与数据获取

数据获取逻辑抽进自定义 Hook(第 5.1 节的三层分离):

function useTasks() { const [tasks, setTasks] = useState([]); const [status, setStatus] = useState('idle'); const [error, setError] = useState(null); const load = useCallback(async () => { setStatus('loading'); try { const data = await fetchTasks(); setTasks(data); setStatus('done'); } catch (e) { setError(e); setStatus('error'); } }, []); useEffect(() => { load(); }, [load]); return { tasks, status, error, reload: load }; }

组件层只管渲染,数据逻辑都在 Hook 里,可测可复用。useTasks 是 5.1 节「逻辑层落地为自定义 Hook」的直接体现——列表页调用它拿数据,测试也直接测它,UI 与逻辑互不纠缠。

任务表单与提交

表单用受控组件(第 3.4 节),提交走封装好的 API:

function TaskForm({ onCreated }) { const [title, setTitle] = useState(''); const [error, setError] = useState(''); const handleSubmit = async (e) => { e.preventDefault(); if (!title.trim()) { setError('标题不能为空'); return; } await createTask({ title }); onCreated(); }; // 渲染表单... }

这十几行里浓缩了第 3.4 节的所有要点:preventDefault、必填校验、提交后回调。新建和编辑复用同一个 TaskForm,编辑时用初始值填表——组件只负责「收集与提交」,数据从哪来由调用方决定。

列表渲染与筛选

列表用 map + key(第 2.4 节),筛选用「先算后渲染」——这个模式在第 2.4 节的过滤列表里出现过,这里把它接到真实数据上:

function TaskList({ tasks }) { const [filter, setFilter] = useState('all'); const visible = tasks.filter(t => filter === 'all' ? true : filter === 'done' ? t.done : !t.done ); return ( <div> <div className="filters"> {['all', 'done', 'active'].map(f => ( <button key={f} onClick={() => setFilter(f)}>{f}</button> ))} </div> <ul> {visible.map(t => <TaskItem key={t.id} task={t} />)} </ul> </div> ); }

筛选按钮用 map 生成,key 用筛选项本身(三个枚举值天然唯一)。visible 是派生数据,不存 state,每次渲染现算——这就是第 2.4 节「派生状态勿存」的落地。选中态按钮的高亮可以再配个 className 条件,用第 4.5 节 classnames 的写法。

删除的二次确认

删除操作配 Modal 确认(第 3.5 节的组合模式)——Modal 是通用容器,删除按钮管打开关闭与确认回调,组合出「带确认的删除」:

function DeleteButton({ onDelete }) { const [confirmOpen, setConfirmOpen] = useState(false); return ( <> <button onClick={() => setConfirmOpen(true)}>删除</button> <Modal open={confirmOpen} onClose={() => setConfirmOpen(false)} footer={<button onClick={onDelete}>确认删除</button>}> <p>确定要删除这个任务吗?此操作不可恢复。</p> </Modal> </> ); }

六、第五步:规范、测试与优化

代码规范

ESLint + Prettier 配置好,配合 husky 在提交前自动检查。规范的核心是「提交即规范」——靠工具拦截,不靠自觉。团队的规范不一致,一半是「没人定」,另一半是「定了没人执行」,husky 解决的就是后一半。

测试策略

按第 3.6 节的性价比原则:核心逻辑(useTasks 的加载流程、筛选逻辑)写单元测试,关键交互(登录、新建任务)写组件测试,不写全量 E2E。这样配置下来,测试套件跑得快、维护量可控,同时把「最容易坏的核心逻辑」兜住了。

// useTasks 的加载测试(示意) test('加载任务成功后设置状态', async () => { // mock fetchTasks 返回数据 // 渲染调用 useTasks 的测试组件 // 断言 status 变为 done、tasks 有数据 });

写这类测试时坚持第 3.6 节的原则:断言「界面可见的行为」,别断言内部实现。测试的作用是让后续改代码有安全网,不是给实现细节上锁。

性能优化

第 3.1 节的纪律:先测量后优化。这个规模的项目的性能优化重点是:路由级代码分割(React.lazy)、图片懒加载、列表渲染优化(长列表才需要虚拟滚动)。不预先堆优化,按需做——等 Profiler 指出问题再动手,比盲目套 memo 有效得多。

部署

构建产物部署:Vite 构建输出 dist,部署到静态托管平台(或公司内部服务器)。配置好环境变量(API 地址),后端接口上线后联调。部署是「项目真正交付」的最后一公里,别忽略。

部署还有几件容易被忽略的事:构建前跑一遍测试,防止把坏代码带上线;检查环境变量——生产环境用生产 API 地址,别把开发地址带到线上;看构建产物体积——首屏 bundle 太大就按第 3.1 节做代码分割。上线后盯着监控和日志——前端错误不记录等于没发生,这是第 3.8 节反复强调的。一次完整的发布,到「线上数据正常」才算结束,不是「代码推上去」就完了。

遇到问题的排查路径

真实项目里一定会遇到问题,别慌,按优先级排查:

先看浏览器控制台报错(渲染错误、网络错误都在这暴露),再看网络面板(请求是否发出、状态码多少、返回了什么),然后看组件状态(数据流到哪一步断了)。三步走完,大部分问题都能定位。定位不了再上调试工具——React DevTools 看组件树与 props,断点跟进逻辑。排查问题的顺序比技巧重要:控制台 → 网络 → 状态 → 工具,一层层逼近根因。

七、回顾:这条链路的意义

走完这条链路,你拥有的是一套「从需求到上线」的完整方法。第 1-4 章的能力在这里变成产出,5.1 节的架构在这里变成代码。以后遇到任何 React 项目,你都可以按同样的顺序推进:拆需求 → 选型 → 搭建 → 实现 → 规范 → 测试 → 部署。这套方法不依赖具体工具——换一套框架、换一个库,流程骨架不变,这正是它比单个 API 更有价值的地方。现在,去写你的第一个真实项目吧。

最后一句送给你:React 的一切学习,都是为了「亲手把想法变成可用的界面」。 别停在读完,打开编辑器,把一个最小的任务应用敲出来——从第一行 Vite 命令开始,你已经在路上了。这个教程到此结束,但它教你的方法会在下一个项目里继续生长。

一节小结

  • 需求拆解:先列用户功能,再定页面结构,选型是第二步
  • 三样产出:用户故事清单、页面路由表、数据模型草稿
  • 选型逻辑:够用就好,跟需求走不跟主流走
  • 搭建顺序:建项目 → 装依赖 → 建目录 → 配别名代理 → 路由骨架 → 填业务
  • 请求层封装:URL/方法/错误处理收进一个文件,业务不碰请求细节
  • 状态落地:登录态 Context、列表页 Hook、表单内部 useState
  • 核心实现:路由守卫、数据 Hook、受控表单、列表筛选、Modal 确认
  • 三层分离:数据逻辑在 Hook,组件只管渲染
  • 规范:ESLint/Prettier + husky,提交即规范
  • 测试:核心逻辑单测 + 关键交互组件测试,不写全量 E2E
  • 性能:先测量后优化,按需做
  • 部署:先测再构建,配环境变量,上线后看监控
  • 排查路径:控制台 → 网络 → 状态 → 工具
  • 完整方法:拆需求 → 选型 → 搭建 → 实现 → 规范 → 测试 → 部署

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U