
Todolist.tsx
javascript
import React, { useState } from "react";
// 引入 Redux 相关的 Hooks
import { useDispatch, useSelector } from "react-redux";
// 引入刚才定义的 Actions,用于触发状态变更
import { addTodo, removeTodo, toggleCompleted } from "../store/todoList";
// 引入 nanoid 用于生成 ID
import { nanoid } from "nanoid";
// 引入 RootState 类型,用于类型推断
import type { RootState } from "../store/index";
// 引入 TodoItemType 类型,用于定义本地状态类型
import type { TodoItemType } from "../store/todoList";
/**
* TodoList 函数式组件
* 实现了待办事项的增、删、改(状态切换)功能
*/
const TodoList = () => {
// --- 本地状态 (Component State) ---
// 用于管理输入框的受控组件状态
const [inputValue, setInputValue] = useState("");
// --- Redux 状态与方法 ---
// 获取 dispatch 函数,用于向 store 发送指令
const dispatch = useDispatch();
// 从 Redux Store 中选取 todos 数据
// 使用泛型 <RootState> 帮助 TypeScript 推断 state 结构
// 使用 `as TodoItemType[]` 进行类型断言(或者确保 RootState 中的 todos 类型定义正确)
const todos = useSelector<RootState>((state) => state.todos) as TodoItemType[];
// --- 事件处理函数 ---
/**
* 处理添加 Todo 的逻辑
*/
const handleAddTodo = () => {
// 1. 校验输入是否为空
if (inputValue.trim() !== "") {
// 2. 构造新的 Todo 对象
const newTodo: TodoItemType = {
id: nanoid(5), // 生成短 ID
text: inputValue,
completed: false, // 默认未完成
};
// 3. 派发 addTodo Action
// 注意:这里传入的是整个 Todo 对象,与 Slice 中定义的 PayloadAction<TodoItemType> 对应
dispatch(addTodo(newTodo));
// 4. 清空输入框
setInputValue("");
}
};
/**
* 处理删除 Todo
* @param id - 要删除的 Todo 的 ID
*/
const handleRemoveTodo = (id: string) => {
// 派发 removeTodo Action
// 注意:这里传入的是 { id: 'xxx' },与 Slice 中定义的 PayloadAction<{id: string}> 对应
dispatch(removeTodo({ id }));
};
/**
* 处理切换完成状态
* @param id - 要切换状态的 Todo 的 ID
*/
const handleToggleCompleted = (id: string) => {
dispatch(toggleCompleted({ id }));
};
// --- 渲染 JSX ---
return (
<>
<h2>Todo List</h2>
<div>
{/* 双向绑定输入框 */}
<input
type="text"
value={inputValue}
// 更新本地状态
onChange={(e) => setInputValue(e.target.value)}
placeholder="Enter a new todo"
/>
{/* 绑定添加事件 */}
<button onClick={handleAddTodo}>Add</button>
</div>
<ul>
{/* 遍历从 Redux 获取的列表 */}
{todos.map((todo) => (
<li key={todo.id}>
{/* 复选框绑定 completed 状态 */}
<input
type="checkbox"
checked={todo.completed}
// 触发状态切换
onChange={() => handleToggleCompleted(todo.id)}
/>
{/* 根据 completed 状态动态添加删除线样式 */}
<span
style={{
textDecoration: todo.completed ? "line-through" : "none",
}}
>
{todo.text}
</span>
{/* 删除按钮 */}
<button onClick={() => handleRemoveTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
</>
);
};
export default TodoList;
todoList.ts
javascript
// 引入 createSlice 用于创建切片(包含 action 和 reducer)
import { createSlice } from "@reduxjs/toolkit";
// 引入 PayloadAction 类型,用于给 reducer 中的 action 定义 payload 的类型
import type { PayloadAction } from "@reduxjs/toolkit";
// 引入 nanoid 用于生成唯一的 ID
import { nanoid } from "nanoid";
// --- 类型定义 ---
/**
* 定义 Todo 项目的类型
*/
export type TodoItemType = {
id: string; // 唯一标识符
text: string; // 任务描述文本
completed: boolean; // 标记任务是否已完成
};
// --- 初始状态 ---
/**
* 定义 Todo 列表的初始状态
* 包含两条示例数据
*/
const INIT_STATE: TodoItemType[] = [
{
id: nanoid(5),
text: "learn redux",
completed: false,
},
{
id: nanoid(5),
text: "learn typescript",
completed: false,
},
];
// --- Slice 创建 ---
/**
* 创建 TodoList 的 Slice
* Slice 会自动生成对应的 action creators 和 reducer 逻辑
*/
export const todoListSlice = createSlice({
name: "todoList", // Action type 的前缀,例如:todoList/addTodo
initialState: INIT_STATE, // 初始状态数组
reducers: {
/**
* 添加 Todo 的 Reducer
* @param state - Immer 代理的 Draft 状态,可以直接"修改"或返回新状态
* @param action - 包含 payload (新 Todo 对象) 的动作
*/
addTodo: (state, action: PayloadAction<TodoItemType>) => {
// 方式一:返回新数组 (不可变更新)
// return [...state, action.payload];
// 方式二:使用 Immer 推荐的"直接修改"语法 (底层会自动生产新状态)
// 这里我们把新任务添加到数组末尾
state.push(action.payload);
},
/**
* 删除 Todo 的 Reducer
* @param action.payload - 一个包含 id 字段的对象,用于指定删除哪个任务
*/
removeTodo: (state, action: PayloadAction<{ id: string }>) => {
// 解构出要删除的 ID
const { id: removeId } = action.payload;
// 查找该 ID 对应的索引
const index = state.findIndex((item) => item.id === removeId);
// 如果找到了,使用 splice 直接删除 (Immer 允许这种写法)
if (index !== -1) {
state.splice(index, 1);
}
},
/**
* 切换任务完成状态的 Reducer
* @param action.payload - 一个包含 id 字段的对象,用于指定切换哪个任务
*/
toggleCompleted: (state, action: PayloadAction<{ id: string }>) => {
const { id: toggleId } = action.payload;
// 在数组中查找对应的 Todo 项
const todo = state.find((item) => item.id === toggleId);
// 如果找到了,直接取反它的 completed 属性
if (todo) {
todo.completed = !todo.completed;
}
},
},
});
// --- 导出 Actions 和 Reducer ---
// 导出生成的 Action Creators,用于在组件中 dispatch
export const { addTodo, removeTodo, toggleCompleted } = todoListSlice.actions;
// 导出 reducer,用于注入到 Redux Store 中
export default todoListSlice.reducer;
app.tsx
javascript
import { useState } from 'react'
import './App.css'
import Count from './pages/Count'
import TodoList from './pages/TodoList'
function App(){
return (
<>
<h1>Redux Demo</h1>
<Count />
<TodoList />
</>
)
}
export default App