学习React-DnD:实现多任务项拖动-维护多任务项数组

一、功能承接与需求概述

上一篇文档中,我们已完成单个任务项的拖动排序功能。本次迭代的核心需求是实现多个任务项的批量选中,具体需达成以下目标:

  • 扩展状态管理,支持存储选中的任务项集合
  • 新增选中、取消选中任务项的操作逻辑
  • 在任务项组件中关联单选框与选中状态的交互

二、核心状态扩展(TodoProvider)

要实现多任务选中与拖拽,首先需要在全局状态中新增"选中任务集合"字段,并同步更新上下文供组件调用。

2.1 初始化状态修改

initialState中添加selectedTodos数组,用于存储当前选中的任务项,同时确保任务项模型的完整性:

js 复制代码
const initialState = {
  todos: [
    { id: 1, text: 'Learn React', completed: false, selected: false },
    { id: 2, text: 'Build a Todo App', completed: false, selected: false },
    { id: 3, text: 'Build a Demo', completed: true, selected: false },
    { id: 4, text: 'Fix a Bug', completed: false, selected: true },

  ],
  // 当前选中的任务数组
  selectedTodos: [{ id: 4, text: 'Fix a Bug', completed: false, selected: true }],
};

2.2 上下文同步更新

将新增的selectedTodos状态注入上下文,确保子组件能获取到选中任务集合:

js 复制代码
// TodoProvider内部的上下文值配置
const contextValue = {
  todos: state.todos,
  selectedTodos: state.selectedTodos,
  ...actions,
};

三、核心操作逻辑实现(Reducer)

基于需求新增两个核心操作类型:ADD_SELECT_TODOS(添加选中任务)、REMOVE_SELECT_TODOS(移除选中任务),以下是完整实现逻辑。

3.1 操作类型常量定义(建议单独维护)

js 复制代码
// ActionTypes.js
export const ActionTypes = {
  // 原有操作类型...
  ADD_SELECT_TODOS: 'ADD_SELECT_TODOS',
  REMOVE_SELECT_TODOS: 'REMOVE_SELECT_TODOS',
};

3.2 添加选中任务(ADD_SELECT_TODOS)

通过任务ID查找目标任务,确保任务存在且未被选中后,添加到selectedTodos集合中,避免重复选中:

js 复制代码
// ADD_SELECT_TODOS
case ActionTypes.ADD_SELECT_TODOS:
  {
    // 找到要添加的todo对象
    const todoToAdd = state.todos.find(todo => todo.id === action.payload.id);
    // 确保todo存在且尚未被选中(避免重复添加)
    if (todoToAdd && !state.selectedTodos.some(todo => todo.id === todoToAdd.id)) {
      return {
        ...state,
        selectedTodos: [...state.selectedTodos, todoToAdd],
      };
    }
    return state;
  }

3.3 移除选中任务(REMOVE_SELECT_TODOS)

根据任务ID从selectedTodos集合中过滤掉目标任务:

js 复制代码
// REMOVE_SELECT_TODOS
case ActionTypes.REMOVE_SELECT_TODOS:
  return {
    ...state,
    selectedTodos: state.selectedTodos.filter(todo => todo.id !== action.payload.id),
  };

四、任务项组件交互(TodoItem)

在TodoItem组件中,通过Ref绑定单选框,并在单选框状态变化时,触发选中/取消选中的操作,实现视图与状态的同步。

4.1 组件核心逻辑

js 复制代码
import { useContext, useRef } from 'react';
import useTodoContext from '@/context/TodoContext/useTodoContext';

const TodoItem = ({ todo }) => {
  const { addSelectTodos, removeSelectTodos } = useTodoContext();
  // 用Ref绑定单选框元素,便于后续操作(如主动获取状态)
  const checkboxRef = useRef(null);

  // 单选框状态变化处理函数
  const handleCheckboxChange = (e) => {
    toggleSelected(todo.id);
    if (e.target.checked) {
      // 选中:调用添加选中任务的action
      addSelectTodos(todo.id);
    } else {
      // 取消选中:调用移除选中任务的action
      removeSelectTodos(todo.id);
    }
  };

  return (
    <div className={`todo-item${isDragging ? ' isDragging' : ''}`} ref={divRef}>
      <div className="todo-item-content">
        <input
          type="checkbox"
          id={`todo-${todo.id}`}
          checked={todo.selected}
          onChange={handleCheckboxChange}
          className="todo-checkbox"
          ref={checkboxRef}
        />
      </div>
      {/* 原有单个任务拖拽相关逻辑 */}
    </div>
  );
};

export default TodoItem;

注意:仅保留必需内容,不包含TodoItem组件的所有内容。

4.2 关键交互说明

  • Ref绑定 :通过checkboxRef可在需要时主动控制单选框(如全选/取消全选功能),提升组件灵活性。
  • 状态双向绑定 :单选框的checked属性直接绑定任务项的selected状态,确保视图与全局状态一致。
  • 操作触发 :状态变化时通过上下文获取的addSelectTodosremoveSelectTodos方法更新全局状态,实现跨组件状态同步。

五、配套Action函数实现

为了让组件更便捷地调用上述操作,需在TodoProvider中定义对应的action函数,并注入上下文:

javascript 复制代码
const actions = {
  // 原有action...
  
    // 添加选中任务
    addSelectTodos: (id) => {
      dispatch({
        type: ActionTypes.ADD_SELECT_TODOS,
        payload: { id },
      });
    },

    // 移除选中任务
    removeSelectTodos: (id) => {
      dispatch({
        type: ActionTypes.REMOVE_SELECT_TODOS,
        payload: { id },
      });
    },
    
};

六、测试要点

  1. 单个任务选中/取消:勾选单选框后,selectedTodos应同步增减,任务项selected状态正确。
  2. 多个任务选中:选中多个任务后,selectedTodos应包含所有选中项,无重复数据。
相关推荐
雪碧聊技术5 小时前
前端项目代码发生改变,如何重新部署到linux服务器?
前端·vue3·centos7·代码更新,重新部署
liulilittle6 小时前
C++ 浮点数封装。
linux·服务器·开发语言·前端·网络·数据库·c++
wordbaby6 小时前
Expo 进阶指南:赋予 TanStack Query “原生感知力” —— 深度解析 AppState 与 NetInfo
前端·react native
Moment6 小时前
从美团全栈化看 AI 冲击:前端转全栈,是自救还是必然 🤔🤔🤔
前端·后端·面试
天问一6 小时前
使用 Vue Router 进行路由定制和调用的示例
前端·javascript·vue.js
韩立学长8 小时前
【开题答辩实录分享】以《基于Vue的非遗文化知识分享平台的设计与实现》为例进行选题答辩实录分享
前端·javascript·vue.js
优弧8 小时前
离开舒适区100天,我后悔了吗?
前端·后端·面试
胡gh8 小时前
css的臂膀,前端动效的利器,还是布局的“隐形陷阱”?
前端·css·html
灵感菇_8 小时前
Flutter Riverpod 完整教程:从入门到实战
前端·flutter·ui·状态管理
用户21411832636028 小时前
紧急修复!Dify CVE-2025-55182 高危漏洞,手把手教你升级避坑
前端