1. 为什么选择React构建todoList?
作为前端开发者入门React的第一个实战项目,todoList几乎成了必经之路。这背后有几个重要原因:
首先,todoList涵盖了React最核心的状态管理概念。一个完整的待办事项应用需要处理:
- 任务列表的增删改查
- 单个任务的状态切换(完成/未完成)
- 可能需要的筛选功能(全部/已完成/未完成)
这些功能恰好对应了React中props传递、state管理和组件通信等关键知识点。我在2016年第一次接触React时,就是通过重构一个jQuery版本的todoList来理解React的声明式编程思想。
其次,todoList具有天然的渐进式复杂度。初学者可以从最简单的静态列表开始:
jsx复制function TodoList() {
return (
<ul>
<li>学习React</li>
<li>编写todoList</li>
</ul>
)
}
然后逐步添加交互功能:
jsx复制function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '学习React', completed: false },
{ id: 2, text: '编写todoList', completed: false }
]);
const toggleTodo = id => {
setTodos(todos.map(todo =>
todo.id === id ? {...todo, completed: !todo.completed} : todo
));
};
// ...渲染逻辑
}
这种从简单到复杂的演进路径,特别适合用来学习新技术。我在教学过程中发现,相比直接讲解概念,让学生通过todoList的迭代来体会React特性,理解深度会明显提升。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 现代React技术栈的选择
2023年构建React todoList,技术选型已经与早期有很大不同。以下是当前推荐的技术组合:
2.1 创建项目
不再推荐使用create-react-app(CRA),而应该选择:
bash复制npm create vite@latest my-todo --template react
Vite的优势在于:
- 启动速度极快(CRA项目可能需要10秒,Vite通常1秒内)
- 对现代浏览器ES模块的原生支持
- 更简单的配置方式
我在多个项目中实测,Vite的开发体验明显优于Webpack系工具,特别是当项目规模增长后。
2.2 状态管理
对于todoList这种规模的应用,React自身的状态机制已经完全够用:
jsx复制// 使用useReducer管理复杂状态
const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, action.payload];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.payload
? {...todo, completed: !todo.completed}
: todo
);
default:
return state;
}
};
function App() {
const [todos, dispatch] = useReducer(todoReducer, []);
// ...
}
只有在应用变得非常复杂时,才需要考虑引入Redux或MobX。我在早期项目中犯过的错误之一就是过早引入Redux,导致代码复杂度不必要的增加。
2.3 UI组件库
对于新手,我建议先使用原生HTML元素完成核心逻辑,再考虑引入组件库。当需要美化界面时,可以选择:
- Material-UI (MUI):Google Material Design实现
- Ant Design:企业级UI设计语言
- Chakra UI:灵活的可访问性组件
我个人偏好Chakra UI,因为它的API设计非常直观:
jsx复制<Box p={4}>
<Stack spacing={3}>
<Input
placeholder="新增待办事项"
value={inputValue}
onChange={handleInputChange}
/>
<Button colorScheme="blue" onClick={handleAddTodo}>
添加
</Button>
</Stack>
</Box>
3. 完整todoList实现详解
让我们实现一个功能完整的todoList应用。我将分步骤解释每个关键部分。
3.1 项目初始化
首先创建项目并安装必要依赖:
bash复制npm create vite@latest react-todo --template react
cd react-todo
npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion
npm run dev
项目结构建议:
code复制/src
/components
TodoItem.jsx
TodoForm.jsx
TodoFilter.jsx
App.jsx
main.jsx
3.2 核心状态设计
在App.jsx中定义我们的状态结构:
jsx复制const initialState = {
todos: [
{ id: 1, text: '学习React', completed: false },
{ id: 2, text: '编写todoList', completed: true }
],
filter: 'all' // 'all' | 'active' | 'completed'
};
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, {
id: Date.now(),
text: action.text,
completed: false
}]
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.id
? {...todo, completed: !todo.completed}
: todo
)
};
case 'DELETE_TODO':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.id)
};
case 'SET_FILTER':
return {...state, filter: action.filter};
default:
return state;
}
}
这种状态设计模式有几个优点:
- 将所有相关状态放在一起管理
- 使用明确的action类型来描述变更
- 保持状态不可变性
3.3 组件拆分与实现
TodoItem组件
jsx复制function TodoItem({ todo, onToggle, onDelete }) {
return (
<Flex align="center" p={2} borderBottom="1px solid" borderColor="gray.100">
<Checkbox
isChecked={todo.completed}
onChange={() => onToggle(todo.id)}
mr={3}
/>
<Text
flex={1}
textDecoration={todo.completed ? 'line-through' : 'none'}
color={todo.completed ? 'gray.400' : 'inherit'}
>
{todo.text}
</Text>
<IconButton
icon={<DeleteIcon />}
aria-label="删除任务"
size="sm"
onClick={() => onDelete(todo.id)}
/>
</Flex>
);
}
TodoForm组件
jsx复制function TodoForm({ onSubmit }) {
const [inputValue, setInputValue] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (inputValue.trim()) {
onSubmit(inputValue);
setInputValue('');
}
};
return (
<form onSubmit={handleSubmit}>
<HStack>
<Input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="输入新任务..."
/>
<Button type="submit" colorScheme="blue">
添加
</Button>
</HStack>
</form>
);
}
3.4 过滤功能实现
在App组件中添加过滤逻辑:
jsx复制function getFilteredTodos(todos, filter) {
switch (filter) {
case 'active':
return todos.filter(todo => !todo.completed);
case 'completed':
return todos.filter(todo => todo.completed);
default:
return todos;
}
}
function App() {
const [state, dispatch] = useReducer(todoReducer, initialState);
const filteredTodos = getFilteredTodos(state.todos, state.filter);
return (
<Box maxW="md" mx="auto" mt={10} p={5} boxShadow="lg">
<Heading mb={6}>待办事项</Heading>
<TodoForm onSubmit={(text) => dispatch({ type: 'ADD_TODO', text })} />
<Stack spacing={0} mt={4}>
{filteredTodos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={(id) => dispatch({ type: 'TOGGLE_TODO', id })}
onDelete={(id) => dispatch({ type: 'DELETE_TODO', id })}
/>
))}
</Stack>
<HStack mt={4} spacing={4}>
<Button
size="sm"
variant={state.filter === 'all' ? 'solid' : 'ghost'}
onClick={() => dispatch({ type: 'SET_FILTER', filter: 'all' })}
>
全部
</Button>
<Button
size="sm"
variant={state.filter === 'active' ? 'solid' : 'ghost'}
onClick={() => dispatch({ type: 'SET_FILTER', filter: 'active' })}
>
未完成
</Button>
<Button
size="sm"
variant={state.filter === 'completed' ? 'solid' : 'ghost'}
onClick={() => dispatch({ type: 'SET_FILTER', filter: 'completed' })}
>
已完成
</Button>
</HStack>
</Box>
);
}
4. 进阶优化与最佳实践
完成基础功能后,我们可以考虑以下几个优化方向:
4.1 本地存储持久化
使用localStorage保存todo数据:
jsx复制// 在App组件中添加
useEffect(() => {
const saved = localStorage.getItem('todos');
if (saved) {
dispatch({ type: 'LOAD_TODOS', todos: JSON.parse(saved) });
}
}, []);
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(state.todos));
}, [state.todos]);
4.2 性能优化
对于大型列表,可以使用React.memo和useCallback:
jsx复制const TodoItem = React.memo(function TodoItem({ todo, onToggle, onDelete }) {
// ...
});
// 在App组件中
const handleToggle = useCallback(
(id) => dispatch({ type: 'TOGGLE_TODO', id }),
[]
);
const handleDelete = useCallback(
(id) => dispatch({ type: 'DELETE_TODO', id }),
[]
);
4.3 可访问性改进
添加适当的ARIA属性:
jsx复制<Checkbox
isChecked={todo.completed}
onChange={() => onToggle(todo.id)}
aria-label={todo.completed ? '标记为未完成' : '标记为已完成'}
/>
4.4 测试策略
添加简单的单元测试:
jsx复制// todoReducer.test.js
describe('todoReducer', () => {
it('应该能添加新任务', () => {
const state = { todos: [], filter: 'all' };
const action = { type: 'ADD_TODO', text: '新任务' };
const newState = todoReducer(state, action);
expect(newState.todos).toHaveLength(1);
expect(newState.todos[0].text).toBe('新任务');
});
// 其他测试用例...
});
5. 常见问题与解决方案
在教授React todoList的过程中,我收集了学生们最常遇到的几个问题:
5.1 直接修改state
错误示例:
jsx复制// 错误!直接修改了state
const handleToggle = (id) => {
const todo = todos.find(t => t.id === id);
todo.completed = !todo.completed;
setTodos(todos);
};
正确做法是始终返回新对象/数组:
jsx复制setTodos(todos.map(todo =>
todo.id === id ? {...todo, completed: !todo.completed} : todo
));
5.2 key属性使用不当
错误示例:
jsx复制{todos.map((todo, index) => (
<TodoItem key={index} {...todo} />
))}
应该使用稳定唯一的ID:
jsx复制{todos.map(todo => (
<TodoItem key={todo.id} {...todo} />
))}
5.3 不必要的effect
错误示例:
jsx复制const [filteredTodos, setFilteredTodos] = useState([]);
useEffect(() => {
setFilteredTodos(getFilteredTodos(todos, filter));
}, [todos, filter]);
可以直接计算:
jsx复制const filteredTodos = getFilteredTodos(todos, filter);
5.4 表单处理问题
常见错误包括:
- 忘记preventDefault导致页面刷新
- 没有处理输入验证
- 状态更新不同步
推荐使用React Hook Form等库简化表单处理:
jsx复制const { register, handleSubmit, reset } = useForm();
const onSubmit = (data) => {
dispatch({ type: 'ADD_TODO', text: data.todoText });
reset();
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input {...register('todoText', { required: true })} />
<Button type="submit">添加</Button>
</form>
);
6. 从todoList到真实项目
掌握了todoList的核心模式后,可以将其扩展到更复杂的应用场景:
6.1 添加后端API集成
使用axios或fetch与后端交互:
jsx复制useEffect(() => {
const fetchTodos = async () => {
try {
const response = await axios.get('/api/todos');
dispatch({ type: 'LOAD_TODOS', todos: response.data });
} catch (error) {
console.error('获取任务列表失败:', error);
}
};
fetchTodos();
}, []);
6.2 用户认证集成
添加登录功能保护todo数据:
jsx复制function App() {
const [user, setUser] = useState(null);
if (!user) {
return <Login onSuccess={setUser} />;
}
return (
<>
<Header user={user} onLogout={() => setUser(null)} />
<TodoApp />
</>
);
}
6.3 实时同步
使用WebSocket实现多设备同步:
jsx复制useEffect(() => {
const ws = new WebSocket('wss://api.example.com/todos');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'TODO_UPDATED') {
dispatch({ type: 'UPDATE_TODO', todo: data.todo });
}
};
return () => ws.close();
}, []);
6.4 部署上线
使用Vercel或Netlify一键部署:
bash复制npm install -g vercel
vercel
或者构建静态文件:
bash复制npm run build
# 然后将build目录上传到任何静态主机
通过这个todoList项目,我们实际上已经涵盖了现代React开发的绝大多数核心概念。从组件设计到状态管理,从性能优化到测试策略,这些知识可以直接迁移到大型项目中。
