react中todolist小案例

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
相关推荐
MQliferecord6 小时前
如何实现倒计时工具
前端
七月丶6 小时前
Cloudflare 🌏 中国大陆网络访问优化 - 0元成本
人工智能·react.js·设计模式
by__csdn6 小时前
Vue3 生命周期全面解析:从创建到销毁的完整指南
开发语言·前端·javascript·vue.js·typescript·前端框架·ecmascript
小年糕是糕手6 小时前
【C++同步练习】模板初阶
服务器·开发语言·前端·javascript·数据库·c++·改行学it
纸人特工6 小时前
NuxtHub部署nuxt项目就是方便
前端
_默_6 小时前
前端常用依赖归纳【vueuse\lodash-es\dayjs\bignumber】
大数据·前端·elasticsearch
北辰alk6 小时前
React页面刷新数据不丢失?5种方案全解析!
react.js
Spirited_Away6 小时前
修改请求头插件迁移manifest V3记录
前端·chrome
cindershade6 小时前
使用 SSE 单向推送实现 系统通知功能
前端