使用 React + Stylus + Vite 构建一个完整的 Todo 应用
在现代前端开发中,React 以其组件化、声明式和高效的特性成为构建用户界面的首选框架。本文将带你从零开始,使用 React + Stylus + Vite 搭建一个功能完整、结构清晰的 Todo List 应用,并深入讲解 父子组件通信、子父通信、兄弟组件通信 的最佳实践。
技术栈说明
- React:用于构建 UI 的 JavaScript 库。
- Vite:新一代前端构建工具,启动快、热更新迅速。
- Stylus:一种富有表现力、动态且健壮的 CSS 预处理器,简化样式编写。
- localStorage:用于持久化存储 Todo 数据,页面刷新后依然保留。
项目结构概览
src/
├── App.jsx
├── components/
│ ├── TodoInput.jsx
│ ├── TodoList.jsx
│ └── TodoStats.jsx
└── styles/
└── app.styl
核心思想:状态提升(Lifting State Up)
在本项目中,所有共享状态(todos)都由 父组件 App 统一管理。子组件通过 props 接收数据 和 回调函数 来实现交互。这种模式确保了:
- 数据流单向、可预测;
- 状态集中管理,便于调试与维护;
- 子组件无状态(或仅持有局部 UI 状态),职责单一。
父组件 App.jsx:状态管理中心
import { useState, useEffect } from 'react'
import './styles/app.styl'
import TodoInput from './components/TodoInput'
import TodoList from './components/TodoList'
import TodoStats from './components/TodoStats'
function App() {
// 初始化 todos,优先从 localStorage 读取
const [todos, setTodos] = useState(() => {
const saved = localStorage.getItem('todos');
return saved ? JSON.parse(saved) : [];
});
// 添加 Todo
const addTodo = (text) => {
setTodos([...todos, {
id: Date.now(),
text,
completed: false
}]);
};
// 删除 Todo
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
// 切换完成状态
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
// 清除已完成项
const clearCompleted = () => {
setTodos(todos.filter(todo => !todo.completed));
};
// 计算统计信息
const activeCount = todos.filter(todo => !todo.completed).length;
const completedCount = todos.filter(todo => todo.completed).length;
// 持久化到 localStorage
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
return (
<div className="todo-app">
<h1>My Todos List</h1>
<TodoInput onAdd={addTodo} />
<TodoList
todos={todos}
onDelete={deleteTodo}
onToggle={toggleTodo}
/>
<TodoStats
total={todos.length}
active={activeCount}
completed={completedCount}
onClearCompleted={clearCompleted}
/>
</div>
);
}
export default App;
关键点解析:
useState初始化时使用函数形式:避免每次渲染都解析 localStorage。useEffect监听todos变化:自动同步到本地存储。- 方法作为 props 传递:子组件通过调用这些函数“请求”修改状态,而非直接操作。
子组件详解
1. TodoInput.jsx:输入新任务
import { useState } from 'react';
const TodoInput = ({ onAdd }) => {
const [inputValue, setInputValue] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (inputValue.trim()) {
onAdd(inputValue.trim());
setInputValue('');
}
};
return (
<form className="todo-input" onSubmit={handleSubmit}>
<input
type="text"
value={inputValue}
onChange={e => setInputValue(e.target.value)}
placeholder="What needs to be done?"
/>
<button type="submit">Add</button>
</form>
);
};
export default TodoInput;
注意:React 是单向数据流,通过
onChange+value实现受控组件,确保视图与状态同步。
2. TodoList.jsx:展示与操作任务列表
const TodoList = ({ todos, onDelete, onToggle }) => {
return (
<ul className="todo-list">
{todos.length === 0 ? (
<li className="empty">No todos yet!</li>
) : (
todos.map(todo => (
<li key={todo.id} className={todo.completed ? 'completed' : ''}>
<label>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
</label>
<button onClick={() => onDelete(todo.id)}>×</button>
</li>
))
)}
</ul>
);
};
export default TodoList;
每个 Todo 项通过
onToggle和onDelete向父组件“上报”用户操作。
3. TodoStats.jsx:统计与清理
const TodoStats = ({ total, active, completed, onClearCompleted }) => {
return (
<div className="todo-stats">
<p>Total: {total} | Active: {active} | Completed: {completed}</p>
{completed > 0 && (
<button onClick={onClearCompleted} className="clear-btn">
Clear Completed
</button>
)}
</div>
);
};
export default TodoStats;
仅当有已完成项时才显示“清除”按钮,提升用户体验。
兄弟组件如何通信?
本项目中,TodoInput、TodoList、TodoStats 是兄弟组件。它们并不直接通信,而是:
- 所有数据来自共同父组件
App的todos状态; - 所有修改请求通过
App提供的回调函数发起; App更新状态后,自动重新渲染所有子组件。
✅ 这就是 “通过父组件间接通信” 的经典模式,也是 React 官方推荐的方式。
样式处理:Stylus 的优雅
app.styl 示例(部分):
.todo-app
max-width 600px
margin 40px auto
padding 20px
border 1px solid #eee
border-radius 8px
font-family Arial, sans-serif
.todo-input
display flex
gap 10px
margin-bottom 20px
input
flex 1
padding 8px
border 1px solid #ccc
border-radius 4px
.todo-list
list-style none
padding 0
li
display flex
justify-content space-between
align-items center
padding 10px
border-bottom 1px solid #f0f0f0
&.completed span
text-decoration line-through
color #888
Stylus 的嵌套语法让 CSS 更具可读性和组织性。
总结
通过这个 Todo 应用,我们掌握了:
- ✅ React 组件化开发的核心思想;
- ✅ 父子通信:props 传递数据 + 回调函数;
- ✅ 兄弟通信:状态提升至共同父组件;
- ✅ 使用
useEffect实现副作用(如 localStorage 同步); - ✅ Vite + Stylus 快速搭建现代化开发环境。
这个项目虽小,却涵盖了 React 开发中的关键模式与最佳实践,是学习状态管理和组件协作的绝佳范例。
🎄 圣诞快乐!愿你的代码永远没有 bug,Todo 永远能完成!