React从基础入门到高级实战:React 实战项目 - 项目一:在线待办事项应用

[复制链接]
发表于 2025-6-16 00:32:10 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
React 实战项目:在线待办事项应用

欢迎来到本 React 开发教程专栏的第 26 篇!在之前的 25 篇文章中,我们从 React 的基础概念逐步深入到高级本领,涵盖了组件、状态、路由和性能优化等核心知识。这一次,我们将通过一个完备的实战项目——在线待办事项应用,将这些知识领悟贯通,资助您从理论走向实践。
本项目的目的是为初学者提供一个简单但全面的 React 开发体验。通过这个项目,您将学习如何分析需求、选择技术栈、实现功能并最终将应用部署到线上。无论您是刚刚接触 React 的新手,照旧盼望通过实践巩固基础的开发者,这篇文章都将为您提供清楚的指引和丰富的代码示例。

弁言

React 是一个强大的前端框架,其声明式编程和组件化特性让开发者能够高效地构建用户界面。然而,仅仅明确理论是不够的——真正的学习发生在实践中。在本项目中,我们将构建一个在线待办事项应用,这是一个经典的入门案例,既简单又实用,能够资助您掌握 React 的核心技能。
这个应用的目的非常明确:答应用户创建、编辑和删除待办事项,支持按状态过滤,并将数据保存在本地,确保刷新页面后不会丢失。我们将从需求分析开始,逐步完成技术选型、代码实现和部署上线,并在末了提供一个练习,资助您进一步巩固所学内容。
通过这个项目,您将体验到:


  • 组件化头脑:如何将复杂的界面拆分为可重用的模块。
  • 状态管理:如何在应用中高效地共享和更新数据。
  • 路由计划:如何实现多页面导航。
  • 数据长期化:如何使用本地存储保存用户数据。
准备好了吗?让我们开始吧!

需求分析

在动手写代码之前,我们须要明确这个待办事项应用的详细功能需求。一个清楚的需求清单不但能指导开发过程,还能资助我们明确每个功能的意义。以下是我们项目的核心需求:

  • 创建待办事项
    用户可以输入使命描述并添加到待办列表中。
  • 编辑待办事项
    用户可以修改已有使命的内容。
  • 删除待办事项
    用户可以移除不再须要的使命。
  • 过滤待办事项
    用户可以根据使命状态(全部、已完成、未完成)筛选列表。
  • 数据长期化
    数据将保存在浏览器本地存储中,刷新页面后依然可用。
为什么选择这些功能?

这些功能覆盖了待办事项应用的核心场景,同时也为学习 React 提供了丰富的实践机会:


  • 创建和编辑涉及表单处理和事件监听。
  • 删除和过滤须要掌握状态更新和数组操作。
  • 本地存储引入了数据长期化的概念。
别的,这些功能简单直观,非常得当初学者上手,同时也为后续扩展(如添加分类、优先级等)留下了空间。

技术栈选择

在开始实现之前,我们须要选择合适的技术栈。以下是本项目使用的工具和技术,以及选择它们的理由:


  • React
    核心框架,用于构建用户界面。React 的组件化和声明式编程让开发过程更加直观。
  • Vite
    构建工具,提供快速的开发服务器和高效的打包本领。相比传统的 Create React App,Vite 的启动速度更快,热更新体验更优。
  • React Router
    用于实现页面导航。固然待办事项应用可以是单页应用,但我们将通过多页面计划展示路由的用法。
  • Context API
    React 内置的状态管理工具,用于在组件间共享待办事项数据。相比 Redux,它更轻量,得当小型项目。
技术栈的优势



  • React:生态丰富,学习曲线平滑,是现代前端开发的标配。
  • Vite:2025 年的前端开发趋势方向轻量化和高性能,Vite 代表了这一方向。
  • React Router:支持动态路由和参数传递,是多页面应用的抱负选择。
  • Context API:无需引入外部依赖,简单易用,得当初学者明确状态管理。
这些工具的组合不但易于上手,还能资助您掌握现代 React 开发的精华。

项目实现

现在,我们进入最核心的部分——代码实现。我们将从项目搭建开始,逐步完成组件拆分、路由计划、状态管理和本地存储的开发。
1. 项目搭建

起首,使用 Vite 创建一个新的 React 项目:
  1. npm create vite@latest todo-app -- --template react
  2. cd todo-app
  3. npm install
  4. npm run dev
复制代码
安装须要的依赖:
  1. npm install react-router-dom
复制代码
这将启动一个基础的 React 项目,接下来我们将逐步实现功能。
2. 组件拆分

组件化是 React 的核心头脑。通过将应用拆分为多个小组件,我们可以进步代码的可读性和复用性。
组件布局



  • App:根组件,负责路由配置和团体布局。
  • TodoList:显示待办事项列表,包罗过滤功能。
  • TodoItem:展示单个待办事项,支持编辑和删除。
  • TodoForm:用于添加或编辑待办事项的表单。
  • FilterButtons:提供状态过滤选项。
文件布局

  1. src/
  2. ├── components/
  3. │   ├── TodoList.jsx
  4. │   ├── TodoItem.jsx
  5. │   ├── TodoForm.jsx
  6. │   └── FilterButtons.jsx
  7. ├── context/
  8. │   └── TodoContext.jsx
  9. ├── App.jsx
  10. └── main.jsx
复制代码
3. 路由计划

我们将应用计划为多页面布局,使用 React Router 实现导航。
路由配置



  • /:首页,显示待办事项列表。
  • /add:添加待办事项页面。
  • /edit/:id:编辑指定待办事项页面。
在 App.jsx 中配置路由:
  1. import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
  2. import TodoList from './components/TodoList';
  3. import TodoForm from './components/TodoForm';
  4. import { TodoProvider } from './context/TodoContext';
  5. function App() {
  6.   return (
  7.     <TodoProvider>
  8.       <Router>
  9.         <div className="min-h-screen bg-gray-100 p-4">
  10.           <Routes>
  11.             <Route path="/" element={<TodoList />} />
  12.             <Route path="/add" element={<TodoForm />} />
  13.             <Route path="/edit/:id" element={<TodoForm />} />
  14.           </Routes>
  15.         </div>
  16.       </Router>
  17.     </TodoProvider>
  18.   );
  19. }
  20. export default App;
复制代码
导航链接

在 TodoList 中添加导航到添加页面的按钮:
  1. import { Link } from 'react-router-dom';
  2. function TodoList() {
  3.   return (
  4.     <div>
  5.       <h1 className="text-2xl font-bold mb-4">待办事项</h1>
  6.       <Link to="/add" className="bg-blue-500 text-white px-4 py-2 rounded">
  7.         添加任务
  8.       </Link>
  9.       {/* 列表内容 */}
  10.     </div>
  11.   );
  12. }
  13. export default TodoList;
复制代码
4. 状态管理

我们使用 Context API 管理全局状态,包括待办事项列表和过滤条件。
创建 Context

在 src/context/TodoContext.jsx 中:
  1. import { createContext, useState, useEffect } from 'react';
  2. export const TodoContext = createContext();
  3. export function TodoProvider({ children }) {
  4.   const [todos, setTodos] = useState([]);
  5.   const [filter, setFilter] = useState('all');
  6.   // 加载本地存储数据
  7.   useEffect(() => {
  8.     const storedTodos = JSON.parse(localStorage.getItem('todos')) || [];
  9.     setTodos(storedTodos);
  10.   }, []);
  11.   // 保存数据到本地存储
  12.   useEffect(() => {
  13.     localStorage.setItem('todos', JSON.stringify(todos));
  14.   }, [todos]);
  15.   return (
  16.     <TodoContext.Provider value={{ todos, setTodos, filter, setFilter }}>
  17.       {children}
  18.     </TodoContext.Provider>
  19.   );
  20. }
复制代码
使用 Context

在 TodoList 中访问和过滤数据:
  1. import { useContext } from 'react';
  2. import { TodoContext } from '../context/TodoContext';
  3. import TodoItem from './TodoItem';
  4. import FilterButtons from './FilterButtons';
  5. function TodoList() {
  6.   const { todos, filter } = useContext(TodoContext);
  7.   const filteredTodos = todos.filter((todo) => {
  8.     if (filter === 'completed') return todo.completed;
  9.     if (filter === 'incomplete') return !todo.completed;
  10.     return true;
  11.   });
  12.   return (
  13.     <div>
  14.       <h1 className="text-2xl font-bold mb-4">待办事项</h1>
  15.       <Link to="/add" className="bg-blue-500 text-white px-4 py-2 rounded mb-4 inline-block">
  16.         添加任务
  17.       </Link>
  18.       <FilterButtons />
  19.       <ul className="space-y-2">
  20.         {filteredTodos.map((todo) => (
  21.           <TodoItem key={todo.id} todo={todo} />
  22.         ))}
  23.       </ul>
  24.     </div>
  25.   );
  26. }
  27. export default TodoList;
复制代码
5. 组件实现

TodoItem

在 TodoItem.jsx 中实现单个待办事项的展示和操作:
  1. import { useContext } from 'react';
  2. import { Link } from 'react-router-dom';
  3. import { TodoContext } from '../context/TodoContext';
  4. function TodoItem({ todo }) {
  5.   const { todos, setTodos } = useContext(TodoContext);
  6.   const toggleComplete = () => {
  7.     setTodos(
  8.       todos.map((t) =>
  9.         t.id === todo.id ? { ...t, completed: !t.completed } : t
  10.       )
  11.     );
  12.   };
  13.   const deleteTodo = () => {
  14.     setTodos(todos.filter((t) => t.id !== todo.id));
  15.   };
  16.   return (
  17.     <li className="flex items-center justify-between p-2 bg-white rounded shadow">
  18.       <div className="flex items-center">
  19.         <input
  20.           type="checkbox"
  21.           checked={todo.completed}
  22.           onChange={toggleComplete}
  23.           className="mr-2"
  24.         />
  25.         <span className={todo.completed ? 'line-through text-gray-500' : ''}>
  26.           {todo.text}
  27.         </span>
  28.       </div>
  29.       <div>
  30.         <Link
  31.           to={`/edit/${todo.id}`}
  32.           className="text-blue-500 mr-2"
  33.         >
  34.           编辑
  35.         </Link>
  36.         <button onClick={deleteTodo} className="text-red-500">
  37.           删除
  38.         </button>
  39.       </div>
  40.     </li>
  41.   );
  42. }
  43. export default TodoItem;
复制代码
TodoForm

在 TodoForm.jsx 中实现添加和编辑表单:
  1. import { useState, useContext, useEffect } from 'react';
  2. import { useParams, useNavigate } from 'react-router-dom';
  3. import { TodoContext } from '../context/TodoContext';
  4. function TodoForm() {
  5.   const { todos, setTodos } = useContext(TodoContext);
  6.   const { id } = useParams();
  7.   const navigate = useNavigate();
  8.   const [text, setText] = useState('');
  9.   useEffect(() => {
  10.     if (id) {
  11.       const todo = todos.find((t) => t.id === id);
  12.       if (todo) setText(todo.text);
  13.     }
  14.   }, [id, todos]);
  15.   const handleSubmit = (e) => {
  16.     e.preventDefault();
  17.     if (!text.trim()) return;
  18.     if (id) {
  19.       // 编辑
  20.       setTodos(
  21.         todos.map((t) => (t.id === id ? { ...t, text } : t))
  22.       );
  23.     } else {
  24.       // 添加
  25.       const newTodo = {
  26.         id: Date.now().toString(),
  27.         text,
  28.         completed: false,
  29.       };
  30.       setTodos([...todos, newTodo]);
  31.     }
  32.     navigate('/');
  33.   };
  34.   return (
  35.     <div>
  36.       <h1 className="text-2xl font-bold mb-4">
  37.         {id ? '编辑任务' : '添加任务'}
  38.       </h1>
  39.       <form onSubmit={handleSubmit}>
  40.         <input
  41.           type="text"
  42.           value={text}
  43.           onChange={(e) => setText(e.target.value)}
  44.           className="w-full p-2 border rounded mb-4"
  45.           placeholder="请输入任务描述"
  46.         />
  47.         <button
  48.           type="submit"
  49.           className="bg-blue-500 text-white px-4 py-2 rounded"
  50.         >
  51.           保存
  52.         </button>
  53.       </form>
  54.     </div>
  55.   );
  56. }
  57. export default TodoForm;
复制代码
FilterButtons

在 FilterButtons.jsx 中实现过滤按钮:
  1. import { useContext } from 'react';
  2. import { TodoContext } from '../context/TodoContext';
  3. function FilterButtons() {
  4.   const { filter, setFilter } = useContext(TodoContext);
  5.   return (
  6.     <div className="mb-4">
  7.       <button
  8.         onClick={() => setFilter('all')}
  9.         className={`mr-2 px-4 py-2 rounded ${
  10.           filter === 'all' ? 'bg-blue-500 text-white' : 'bg-gray-200'
  11.         }`}
  12.       >
  13.         全部
  14.       </button>
  15.       <button
  16.         onClick={() => setFilter('completed')}
  17.         className={`mr-2 px-4 py-2 rounded ${
  18.           filter === 'completed' ? 'bg-blue-500 text-white' : 'bg-gray-200'
  19.         }`}
  20.       >
  21.         已完成
  22.       </button>
  23.       <button
  24.         onClick={() => setFilter('incomplete')}
  25.         className={`px-4 py-2 rounded ${
  26.           filter === 'incomplete' ? 'bg-blue-500 text-white' : 'bg-gray-200'
  27.         }`}
  28.       >
  29.         未完成
  30.       </button>
  31.     </div>
  32.   );
  33. }
  34. export default FilterButtons;
复制代码
6. 本地存储

本地存储已在 TodoContext 中实现,通过 localStorage 保存和加载数据,确保刷新页面后数据不会丢失。

部署

开发完成后,我们将应用部署到 Netlify,让它在线上运行。
1. 构建项目

运行以下命令生成静态文件:
  1. npm run build
复制代码
这会生成 dist 文件夹,包罗应用的静态资源。
2. 部署到 Netlify


  • 注册 Netlify:访问 Netlify 官网 并创建账号。
  • 新建站点:在控制台选择“New site from Git”。
  • 连接仓库:将项目推送至 GitHub 并连接。
  • 配置构建

    • 构建命令:npm run build

    • 发布目次:dist

  • 部署:点击“Deploy site”,等待部署完成。
部署乐成后,您将得到一个唯一的 URL,可以通过它访问您的待办事项应用。

练习:添加分类功能

为了资助您巩固所学,我们计划了一个练习:为待办事项添加分类功能。
需求



  • 用户可以为使命指定分类(如“工作”、“个人”、“学习”)。
  • 用户可以按分类过滤使命。
实现步调


  • 扩展数据布局
    在 todos 中为每个使命添加 category 字段。
  • 更新 TodoForm
    添加分类选择下拉菜单。
  • 更新过滤逻辑
    在 TodoList 和 FilterButtons 中支持分类过滤。
示例代码

修改 TodoContext

  1. export function TodoProvider({ children }) {
  2.   const [todos, setTodos] = useState([]);
  3.   const [filter, setFilter] = useState('all');
  4.   useEffect(() => {
  5.     const storedTodos = JSON.parse(localStorage.getItem('todos')) || [];
  6.     setTodos(storedTodos);
  7.   }, []);
  8.   useEffect(() => {
  9.     localStorage.setItem('todos', JSON.stringify(todos));
  10.   }, [todos]);
  11.   return (
  12.     <TodoContext.Provider value={{ todos, setTodos, filter, setFilter }}>
  13.       {children}
  14.     </TodoContext.Provider>
  15.   );
  16. }
复制代码
修改 TodoForm

  1. function TodoForm() {
  2.   const { todos, setTodos } = useContext(TodoContext);
  3.   const { id } = useParams();
  4.   const navigate = useNavigate();
  5.   const [text, setText] = useState('');
  6.   const [category, setCategory] = useState('工作');
  7.   useEffect(() => {
  8.     if (id) {
  9.       const todo = todos.find((t) => t.id === id);
  10.       if (todo) {
  11.         setText(todo.text);
  12.         setCategory(todo.category);
  13.       }
  14.     }
  15.   }, [id, todos]);
  16.   const handleSubmit = (e) => {
  17.     e.preventDefault();
  18.     if (!text.trim()) return;
  19.     if (id) {
  20.       setTodos(
  21.         todos.map((t) =>
  22.           t.id === id ? { ...t, text, category } : t
  23.         )
  24.       );
  25.     } else {
  26.       const newTodo = {
  27.         id: Date.now().toString(),
  28.         text,
  29.         category,
  30.         completed: false,
  31.       };
  32.       setTodos([...todos, newTodo]);
  33.     }
  34.     navigate('/');
  35.   };
  36.   return (
  37.     <div>
  38.       <h1 className="text-2xl font-bold mb-4">
  39.         {id ? '编辑任务' : '添加任务'}
  40.       </h1>
  41.       <form onSubmit={handleSubmit}>
  42.         <input
  43.           type="text"
  44.           value={text}
  45.           onChange={(e) => setText(e.target.value)}
  46.           className="w-full p-2 border rounded mb-4"
  47.           placeholder="请输入任务描述"
  48.         />
  49.         <select
  50.           value={category}
  51.           onChange={(e) => setCategory(e.target.value)}
  52.           className="w-full p-2 border rounded mb-4"
  53.         >
  54.           <option value="工作">工作</option>
  55.           <option value="个人">个人</option>
  56.           <option value="学习">学习</option>
  57.         </select>
  58.         <button
  59.           type="submit"
  60.           className="bg-blue-500 text-white px-4 py-2 rounded"
  61.         >
  62.           保存
  63.         </button>
  64.       </form>
  65.     </div>
  66.   );
  67. }
复制代码
修改 TodoList 和 FilterButtons

  1. function TodoList() {
  2.   const { todos, filter } = useContext(TodoContext);
  3.   const filteredTodos = todos.filter((todo) => {
  4.     if (filter === 'completed') return todo.completed;
  5.     if (filter === 'incomplete') return !todo.completed;
  6.     if (['工作', '个人', '学习'].includes(filter)) return todo.category === filter;
  7.     return true;
  8.   });
  9.   return (
  10.     <div>
  11.       <h1 className="text-2xl font-bold mb-4">待办事项</h1>
  12.       <Link to="/add" className="bg-blue-500 text-white px-4 py-2 rounded mb-4 inline-block">
  13.         添加任务
  14.       </Link>
  15.       <FilterButtons />
  16.       <ul className="space-y-2">
  17.         {filteredTodos.map((todo) => (
  18.           <TodoItem key={todo.id} todo={todo} />
  19.         ))}
  20.       </ul>
  21.     </div>
  22.   );
  23. }
  24. function FilterButtons() {
  25.   const { filter, setFilter } = useContext(TodoContext);
  26.   return (
  27.     <div className="mb-4 flex flex-wrap gap-2">
  28.       <button
  29.         onClick={() => setFilter('all')}
  30.         className={`px-4 py-2 rounded ${
  31.           filter === 'all' ? 'bg-blue-500 text-white' : 'bg-gray-200'
  32.         }`}
  33.       >
  34.         全部
  35.       </button>
  36.       <button
  37.         onClick={() => setFilter('completed')}
  38.         className={`px-4 py-2 rounded ${
  39.           filter === 'completed' ? 'bg-blue-500 text-white' : 'bg-gray-200'
  40.         }`}
  41.       >
  42.         已完成
  43.       </button>
  44.       <button
  45.         onClick={() => setFilter('incomplete')}
  46.         className={`px-4 py-2 rounded ${
  47.           filter === 'incomplete' ? 'bg-blue-500 text-white' : 'bg-gray-200'
  48.         }`}
  49.       >
  50.         未完成
  51.       </button>
  52.       <button
  53.         onClick={() => setFilter('工作')}
  54.         className={`px-4 py-2 rounded ${
  55.           filter === '工作' ? 'bg-blue-500 text-white' : 'bg-gray-200'
  56.         }`}
  57.       >
  58.         工作
  59.       </button>
  60.       <button
  61.         onClick={() => setFilter('个人')}
  62.         className={`px-4 py-2 rounded ${
  63.           filter === '个人' ? 'bg-blue-500 text-white' : 'bg-gray-200'
  64.         }`}
  65.       >
  66.         个人
  67.       </button>
  68.       <button
  69.         onClick={() => setFilter('学习')}
  70.         className={`px-4 py-2 rounded ${
  71.           filter === '学习' ? 'bg-blue-500 text-white' : 'bg-gray-200'
  72.         }`}
  73.       >
  74.         学习
  75.       </button>
  76.     </div>
  77.   );
  78. }
复制代码
练习目的

通过这个练习,您将学会如何扩显现有功能,提升对状态管理和组件通信的明确。

注意事项



  • 初学者友好:本文制止了复杂的概念,全部代码都尽量保持简单直观。
  • 学习建议:建议您边阅读边动手实现,遇到题目时查阅 React 官方文档和Vite 文档
  • 扩展思路:完成项目后,可以实验添加更多功能,如使命优先级、截止日期或提醒功能。

结语

通过这个在线待办事项应用项目,您从需求分析到部署上线,完备地走过了一个 React 项目的开发流程。您学习了组件拆分、路由计划、状态管理和数据长期化等核心技能,这些知识将成为您未来开发更复杂应用的基础。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告
回复

使用道具 举报

© 2001-2025 Discuz! Team. Powered by Discuz! X3.5

GMT+8, 2025-7-22 02:01 , Processed in 0.114719 second(s), 29 queries 手机版|qidao123.com技术社区-IT企服评测▪应用市场 ( 浙ICP备20004199 )|网站地图

快速回复 返回顶部 返回列表