大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
Checkpoint: 前端应用
你将完成什么
用 React 写一个 Todo 应用,包含:添加、删除、标记完成、筛选(全部/进行中/已完成)、本地存储。
验收标准
- 函数组件 + Hooks(
useState、useEffect) - Props 传递、事件处理
- 条件渲染、列表渲染
localStorage持久化
核心代码
App.jsx
import { useState, useEffect } from 'react';
import TodoList from './TodoList';
import TodoForm from './TodoForm';
import TodoFilter from './TodoFilter';
function App() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all');
// 从 localStorage 加载
useEffect(() => {
const saved = localStorage.getItem('todos');
if (saved) {
setTodos(JSON.parse(saved));
}
}, []);
// 保存到 localStorage
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
const addTodo = (text) => {
setTodos([...todos, {
id: Date.now(),
text,
completed: false
}]);
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
));
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
const filteredTodos = todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
return (
<div className="app">
<h1>Todo 应用</h1>
<TodoForm onAdd={addTodo} />
<TodoFilter filter={filter} onChange={setFilter} />
<TodoList
todos={filteredTodos}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
</div>
);
}
export default App;
TodoForm.jsx
import { useState } from 'react';
function TodoForm({ onAdd }) {
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!text.trim()) return;
onAdd(text);
setText('');
};
return (
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="输入待办事项"
/>
<button type="submit">添加</button>
</form>
);
}
export default TodoForm;
TodoList.jsx
function TodoList({ todos, onToggle, onDelete }) {
if (todos.length === 0) {
return <p>暂无待办事项</p>;
}
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}>
{todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>删除</button>
</li>
))}
</ul>
);
}
export default TodoList;
TodoFilter.jsx
function TodoFilter({ filter, onChange }) {
return (
<div>
<button
className={filter === 'all' ? 'active' : ''}
onClick={() => onChange('all')}
>
全部
</button>
<button
className={filter === 'active' ? 'active' : ''}
onClick={() => onChange('active')}
>
进行中
</button>
<button
className={filter === 'completed' ? 'active' : ''}
onClick={() => onChange('completed')}
>
已完成
</button>
</div>
);
}
export default TodoFilter;
常见卡点
- State 不更新:确认用
setState,不要直接修改todos.push()。 - 列表渲染报警告:每个子元素加
key={todo.id}。 - localStorage 存对象:用
JSON.stringify存,JSON.parse取。
加分项
- 编辑功能(双击修改)
- 拖拽排序(用
react-beautiful-dnd) - 暗色主题切换
下一步
完成本课后,进入「后端 API」阶段,把数据存到数据库。