前言
Todo List 这个东西虽然简单,但里面能覆盖 React 的大部分核心点:
- 父子组件通信(props)
- 单向数据流
- 自定义 Hooks
- 响应式状态管理
- 本地持久化(localStorage)
- 样式拆分、响应式设计
这篇我就直接把我的完整实现分享出来,顺便整理一些小经验,语言尽量通俗,不玩虚的。
🗂️ 项目需求
- 新增任务
- 切换任务的完成状态
- 删除任务
- 自动保存到浏览器的
localStorage,页面刷新任务也不会丢 - 样式尽量简洁,支持基础响应式
核心功能代码
下面这套就是你之前的实现,包含完整注释。
hooks/useTodos.js - 自定义 Hook
import { useState, useEffect } from "react";
export const useTodos = () => {
const [todos, setTodos] = useState(
JSON.parse(localStorage.getItem("todos")) || []
);
useEffect(() => {
// 数据变化就更新 localStorage
localStorage.setItem("todos", JSON.stringify(todos));
}, [todos]);
// 新增
const addTodo = (text) => {
setTodos([
...todos,
{
id: Date.now(),
text,
isComplete: false
}
]);
};
// 切换完成状态
const onToggle = (id) => {
setTodos(
todos.map(todo =>
todo.id === id ? { ...todo, isComplete: !todo.isComplete } : todo
)
);
};
// 删除
const onDelete = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return {
todos,
setTodos,
addTodo,
onToggle,
onDelete,
};
};
components/Todos.jsx - 父组件
import TodoForm from './TodoForm'
import TodoList from './TodoList'
import { useTodos } from '@/hooks/useTodos';
const Todos = () => {
const { todos, addTodo, onToggle, onDelete } = useTodos();
console.log(todos, '-----------');
return (
<div className="app">
{/* 自定义事件 */}
<TodoForm onAddTodo={addTodo} />
<TodoList todos={todos} onToggle={onToggle} onDelete={onDelete} />
</div>
);
};
export default Todos;
components/TodoForm.jsx - 新增任务
import { useState } from 'react';
const TodoForm = ({ onAddTodo }) => {
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
let result = text.trim();
if (!result) return;
onAddTodo(result);
setText('');
};
return (
<>
<h1 className='header'>TodoList</h1>
<form className='todo-input' onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e) => {
setText(e.target.value);
}}
placeholder='Todo Text'
required
/>
<button type='submit'>Add</button>
</form>
</>
);
};
export default TodoForm;
components/TodoList.jsx - 列表
import TodoItem from "./TodoItem";
const TodoList = (props) => {
const { todos, onToggle, onDelete } = props;
return (
<ul className="todo-list">
{todos.length > 0 ? (
todos.map(todo =>
<TodoItem
key={todo.id}
todo={todo}
onToggle={() => onToggle(todo.id)}
onDelete={() => onDelete(todo.id)}
/>
)
) : (
<p>暂无待办事项</p>
)}
</ul>
);
};
export default TodoList;
components/TodoItem.jsx - 单个任务项
const TodoItem = (props) => {
const {
text,
isComplete,
} = props.todo;
const { onToggle, onDelete } = props;
return (
<div className="todo-item">
<input type="checkbox" checked={isComplete} onChange={onToggle} />
<span className={isComplete ? 'completed' : ''}>{text}</span>
<button onClick={onDelete}>Delete</button>
</div>
);
};
export default TodoItem;
🎨 简易示例样式
配一套最基础的 CSS,直接放到 index.css 或 App.css:
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #f7f9fc;
color: #333;
}
.app {
max-width: 600px;
margin: 2rem auto;
padding: 2rem;
background: #fff;
border-radius: 8px;
}
.header {
text-align: center;
margin-bottom: 1rem;
}
.todo-input {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.todo-input input {
flex: 1;
padding: 0.5rem 1rem;
}
.todo-input button {
padding: 0 1rem;
background: #007bff;
border: none;
color: #fff;
cursor: pointer;
}
.todo-list {
list-style: none;
padding: 0;
}
.todo-item {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #eee;
padding: 0.5rem 0;
}
.todo-item span.completed {
text-decoration: line-through;
color: #aaa;
}
⚡ 关键点小结
✅ Stylus 和 Vite:Vite 脚手架自带对 Stylus 的支持,想要样式写得更舒服可以用 Stylus 做 CSS 超集,样式拆分清晰。
✅ 字体:用 -apple-system、Segoe UI、Roboto 这类组合,前端负责用户体验,字体也很关键。
✅ rem 单位:移动端尽量别死用 px,相对单位 rem / em / vw / vh 对适配友好,后期配上媒体查询一套走天下。
✅ props 通信:组件通信核心就是 props,父组件通过 props 把状态和自定义事件传给子组件,子组件通知父组件更新,始终保持单向数据流。
✅ localStorage 和 Cookie 的区别:
localStorage只在前端可读写,域名隔离,存储大(一般 5MB),不随请求发给后端。- Cookie 前后端都能设置,每次请求都会自动带上,但容量小(4KB 左右),大了影响性能。
✅ 路径优化:如果觉得 ../../ 很烦,可以在 vite.config.js 配置 alias,写 @/hooks、@/components,省事。
import path from 'path' //vite 工程化工具 node
alias: {
'@': path.resolve(__dirname, './src'),
},
✅ useContext:如果组件嵌套深或者要全局共享状态,可以配 useContext 或 Zustand 做状态管理。
✅ 自定义 Hooks:现代 React 项目里,逻辑能提就提,组件只关注视图渲染,状态管理和副作用交给 Hooks,维护成本低。
🎯 总结
- Todo List 虽小,麻雀虽小五脏俱全,能把 React 的状态流、通信、响应式、持久化都串起来跑一遍。
- 自定义 Hooks 是函数式编程思想的体现,组件更聚焦渲染,逻辑更清晰。
- 样式也别糊弄,系统字体、相对单位、样式拆分,前期做好,后期扩展省心。
如果这篇对你有帮助,欢迎收藏、点赞,有问题评论区咱们一起交流!🚀