React实现卡片拖拽效果流程详解

前提摘要:

学习宋一玮 React 新版本 + 函数组件 &Hooks 优先 开篇就是函数组件+Hooks 实现的效果如下: 学到第11篇了 照葫芦画瓢,不过老师在讲解的过程中没有考虑拖拽目标项边界问题,我稍微处理了下这样就实现拖拽流畅了

下面就是主要的代码了,实现拖拽(src/App.js):

核心在于标记当前项,来源项,目标项,并且在拖拽完成时对数据处理,更新每一组数据(useState);

/** @jsxImportSource @emotion/react */
// 上面代码是使用emotion的关键CSS-in-JS
import React, { useEffect, useState, useRef } from "react";
import { css } from "@emotion/react";
import "./App.css";
const MINUTE = 60 * 1000;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
const UPDATE_INTERVAL = MINUTE;
// const ongoingList = [{ title: "进行任务", status: "2022-11-09 15:29" }];
// const doneList = [{ title: "完成任务", status: "2022-11-09 15:59" }];
const KanbanBoard = ({ children }) => (
  <main
    css={css`
      flex: 10;
      display: flex;
      flex-direction: row;
      gap: 1rem;
      margin: 0 1rem 1rem;
    `}
  >
    {children}
  </main>
);
const KanbanColumn = ({
  children,
  className,
  title,
  setIsDragSource = () => { },
  setIsDragTarget = () => { },
  onDrop,
}) => {
  const combinedClassName = `kanban-column ${className}`;
  return (
    <section
      className={combinedClassName}
      onDragStart={() => setIsDragSource(true)}
      onDragOver={(evt) => {
        evt.preventDefault();
        evt.dataTransfer.dropEffect = "move";
        setIsDragTarget(true);
      }}
      onDragLeave={(evt) => {
        evt.preventDefault();
        evt.dataTransfer.dropEffect = "none";
        setIsDragTarget(false);
      }}
      onDrop={(evt) => {
        evt.preventDefault();
        onDrop && onDrop(evt);
      }}
      onDragEnd={(evt) => {
        evt.preventDefault();
        setIsDragSource(false);
        setIsDragTarget(false);
      }}
    >
      <h2>{title}</h2>
      <ul>{children}</ul>
    </section>
  );
};
const KanbanCard = ({ title, status, onDragStart }) => {
  const [displayTime, setDisplayTime] = useState(status);
  useEffect(() => {
    const updateDisplayTime = () => {
      const timePassed = new Date() - new Date(status);
      let relativeTime = "刚刚";
      if (MINUTE <= timePassed && timePassed < HOUR) {
        relativeTime = `${Math.ceil(timePassed / MINUTE)} 分钟前`;
      } else if (HOUR <= timePassed && timePassed < DAY) {
        relativeTime = `${Math.ceil(timePassed / HOUR)} 小时前`;
      } else if (DAY <= timePassed) {
        relativeTime = `${Math.ceil(timePassed / DAY)} 天前`;
      }
      setDisplayTime(relativeTime);
    };
    const intervalId = setInterval(updateDisplayTime, UPDATE_INTERVAL);
    updateDisplayTime();
    return function cleanup() {
      clearInterval(intervalId);
    };
  }, [status]);
  const handleDragStart = (evt) => {
    evt.dataTransfer.effectAllowed = "move";
    evt.dataTransfer.setData("text/plain", title);
    onDragStart && onDragStart(evt);
  };
  return (
    <li className="kanban-card" draggable onDragStart={handleDragStart}>
      <div className="card-title">{title}</div>
      <div className="card-status">{displayTime}</div>
    </li>
  );
};
const AddKanbanCard = ({ onSubmit }) => {
  const [title, setTitle] = useState("");
  const handleChange = (evt) => {
    setTitle(evt.target.value);
  };
  const handleKeyDown = (evt) => {
    if (evt.key === "Enter") onSubmit(title);
  };
  const inputElem = useRef(null);
  useEffect(() => {
    inputElem.current.focus();
  });
  return (
    <li className="kanban-card">
      <h4>添加新卡片</h4>
      <div className="card-title">
        <input
          ref={inputElem}
          type="text"
          value={title}
          onChange={handleChange}
          onKeyDown={handleKeyDown}
        ></input>
      </div>
    </li>
  );
};
const DATE_STORE_KEY = "kanban_data_store";
const COLUMN_KEY_TODO = "todo";
const COLUMN_KEY_ONGONING = "ongoing";
const COLUMN_KEY_DONE = "done";
function App() {
  const [todoList, setTodoList] = useState([
    { title: "开发任务-1", status: "2022-05-22 18:15" },
  ]);
  const [ongoingList, setOngoingList] = useState([
    { title: "进行任务-1", status: "2022-08-22 18:15" },
  ]);
  const [doneList, setDoneList] = useState([
    { title: "完成任务-1", status: "2022-10-22 18:15" },
  ]);
  const [showAdd, setShowAdd] = useState(false);
  const handleAdd = (evt) => {
    setShowAdd(true);
  };
  const handleSubmit = (title) => {
    // todoList.unshift({title,status:new Date().toDateString()});
    setTodoList((current) => [{ title, status: new Date() + " " }, ...current]);
    setShowAdd(false);
  };
  const handleSaveAll = () => {
    const data = JSON.stringify({
      todoList,
      ongoingList,
      doneList,
    });
    window.localStorage.setItem(DATE_STORE_KEY, data);
  };
  useEffect(() => {
    const data = window.localStorage.getItem(DATE_STORE_KEY);
    setTimeout(() => {
      if (data) {
        const kanbanColumnData = JSON.parse(data);
        setTodoList(kanbanColumnData.todoList);
      }
    }, 1000);
  });
  const [draggedItem, setDraggedItem] = useState(null);
  const [dragSource, setDragSource] = useState(null);
  const [dragTarget, setDragTarget] = useState(null);
  const handleDrop = (evt) => {
    if (!draggedItem || !dragSource || !dragTarget || dragSource === dragTarget) { return; }
    const updaters = {
      [COLUMN_KEY_TODO]: setTodoList,
      [COLUMN_KEY_ONGONING]: setOngoingList,
      [COLUMN_KEY_DONE]: setDoneList
    };
    if (dragSource) {
      updaters[dragSource]((currentStat) => {
        return currentStat.filter((item) => !Object.is(item, draggedItem));
      });
    }
    if (dragTarget) {
      updaters[dragTarget]((currentStat) => {
        if (currentStat.length > 0) {
          return [draggedItem, ...currentStat]
        } else {
          return [draggedItem]
        }
      })
    }
  };
  return (
    <div className="App">
      <header className="App-header">
        <h1>
          我的看板<button onClick={handleSaveAll}>保存所有卡片</button>{" "}
        </h1>
      </header>
      <KanbanBoard>
        <KanbanColumn
          className="column-todo"
          title={
            <>
              待处理
              <button disabled={showAdd} onClick={handleAdd}>
                ⊕添加新卡片
              </button>{" "}
            </>
          }
          setIsDragSource={(isSrc) =>
            setDragSource(isSrc ? COLUMN_KEY_TODO : null)
          }
          setIsDragTarget={(isTarget) =>
            setDragTarget(isTarget ? COLUMN_KEY_TODO : null)
          }
          onDrop={handleDrop}
        >
          {/* <h2>
            待处理
            <button disabled={showAdd} onClick={handleAdd}>
              ⊕添加新卡片
            </button>{" "}
          </h2> */}
          {/* <ul> */}
          {showAdd && <AddKanbanCard onSubmit={handleSubmit} />}
          {todoList && todoList.map((item) => (
            <KanbanCard
              {...item}
              key={item.title}
              onDragStart={() => setDraggedItem(item)}
            />
          ))}
          {/* </ul> */}
        </KanbanColumn>
        <KanbanColumn className="column-ongoing" title={"进行中"} setIsDragSource={(isSrc) =>
          setDragSource(isSrc ? COLUMN_KEY_ONGONING : null)
        }
          setIsDragTarget={(isTarget) =>
            setDragTarget(isTarget ? COLUMN_KEY_ONGONING : null)
          }
          onDrop={handleDrop}>
          {/* <h2>进行中</h2>
          <ul> */}
          {ongoingList && ongoingList.map((item) => (
            <KanbanCard
              {...item}
              key={item.title}
              onDragStart={() => setDraggedItem(item)}
            />
          ))}
          {/* </ul> */}
        </KanbanColumn>
        <KanbanColumn className="column-done" title={"已处理"} setIsDragSource={(isSrc) =>
          setDragSource(isSrc ? COLUMN_KEY_DONE : null)
        }
          setIsDragTarget={(isTarget) =>
            setDragTarget(isTarget ? COLUMN_KEY_DONE : null)
          }
          onDrop={handleDrop}>
          {/* <h2>已处理</h2>
          <ul> */}
          {doneList && doneList.map((item) => (
            <KanbanCard
              {...item}
              key={item.title}
              onDragStart={() => setDraggedItem(item)}
            />
          ))}
          {/* </ul> */}
        </KanbanColumn>
      </KanbanBoard>
    </div>
  );
}
export default App;

这时拖拽基本完成,此时有一个bug,就是待处理添加新卡片的时候,拖拽之后的数据出现混乱!!如下所示:

首先问题定位,移动的来源项出现了问题,看代码之后发现拖拽处理来源项没有问题,那一定是那块调用更新todaList出现了问题,问题定位在useEffect,使用时如果useEffect的第二个参数不传就在组件所有更新都执行(即任何时候),传个空数组仅在挂载和卸载的时候执行,或者传个你想要去进行更新时候去执行(默认情况下,effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候 才执行。)

更改代码如下:

  useEffect(() => {
    const data = window.localStorage.getItem(DATE_STORE_KEY);
    setTimeout(() => {
      if (data) {
        const kanbanColumnData = JSON.parse(data);
        setTodoList(kanbanColumnData.todoList);
      }
    }, 1000);
  },[]);

useEffect新增空数组效果展示:

学习一个新的框架总是会进行对比,

一:React的单项数据流和Vue中的双向绑定有什么区别?

在我看了Vue 双向绑定其实是语法糖罢了,其原理其实是Object.defineProperty()对数据进行劫持,监听到变化就去对数据进行更改;

而React 中的单项数据流做到了对原有数据的保护,你不能去直接去对Props进行更改,而是在需要赋值给State,然后再SetState中去进行更改,当然Hooks中提供了useState方法,使得开发者更加方便的去对数据进行处理和更改(我可太喜欢Hooks了很省事!!)

二:JSX 是什么?

学习React的时候,写组件的时候写页面元素是用JSX来写的,即Render里面是用JSX来实现的,渲染之后其实质是React.createElement,他只是语法糖 实现React组件的一部分而已,对比Vue中Template,(我更喜欢Vue的实现,更符合开发者)不过Vue也可用JSX来实现;

三:函数式组件(Hooks)与类组件(Class)优缺点?

宋一玮老师的数据表明函数式比类组件使用更多,并且函数组件基本上涵盖了类组件的功能点,除了(只有类组件才能成为错误边界)

从React官网中开局也是用类组件来领进门的,我是看完了官网的基础才来学习课程的才觉得学习没有那么吃力反而能加深理解;(老师的反其道而行可能不太适合初学者)

四:CSS可否想JS一样应用在组件中?

可以的,使用emotion来应用到JSX中(主要代码中有使用),不过CSS中传入JS数据确实很方便但是在运行emotion时会创建大量的<style>标签,有可能影响页面性能。

CSS-in-JS 技术能帮我们做到样式隔离、提升组件样式的可维护性、可复用性。

五:常用的hooks——useState,useEffect、useRef

到此这篇关于React实现卡片拖拽效果流程详解的文章就介绍到这了,更多相关React卡片拖拽内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • react 实现表格列表拖拽排序的示例

    目录 问题描述 思路 解析 1. react-sortable-hoc 2. array-move 问题描述 在项目开发中,遇到这样一个需求:需要对表格里面的数据进行拖拽排序. 效果图如下所示: 思路 安装两个插件: react-sortable-hoc (或者 react-beautiful-dnd) array-move npm install --save react-sortable-hoc npm install --save array-move 解析 1. react-sortab

  • react拖拽组件react-sortable-hoc的使用

    目录 1.文件1 2.文件2 3.使用 使用react-sortable-hoc实现拖拽 如图: 提示:下面案例可供参考 1.文件1 代码如下(示例):文件名称:./dragcomponents import * as React from 'react' import { sortableContainer, sortableElement, sortableHandle, } from "react-sortable-hoc"; // 拖拽的关键组件 const Sortable:

  • React结合Drag API实现拖拽示例详解

    目录 认识拖拽 被拖拽元素 可释放目标 生命周期 拖拽操作中的数据传输 代码实现 如何标记当前拖拽的元素? 在画布中拖动 数据结构 总结 认识拖拽 鼠标拖拽是一个常见的交互场景,在这个熟悉的过程将会发生哪些事件? 拖拽事件指用户通过鼠标(或其他指针设备)将元素移到一个新的位置上.拖拽过程涉及两个对象:被拖拽元素(上图中 A )和可释放目标(上图中 B ) 被拖拽元素 默认情况下,图片.链接和文本是可拖动的.HTML5 在所有 HTML 元素上规定了一个 draggable 属性, 表示元素是否可

  • React实现卡片拖拽效果流程详解

    前提摘要: 学习宋一玮 React 新版本 + 函数组件 &Hooks 优先 开篇就是函数组件+Hooks 实现的效果如下: 学到第11篇了 照葫芦画瓢,不过老师在讲解的过程中没有考虑拖拽目标项边界问题,我稍微处理了下这样就实现拖拽流畅了 下面就是主要的代码了,实现拖拽(src/App.js): 核心在于标记当前项,来源项,目标项,并且在拖拽完成时对数据处理,更新每一组数据(useState): /** @jsxImportSource @emotion/react */ // 上面代码是使用e

  • Qt利用QDrag实现拖拽拼图功能详解

    目录 一.项目介绍 二.项目基本配置 三.UI界面设置 四.主程序实现 4.1 main.cpp 4.1 mainwindow.h头文件 4.2 mainwindow.cpp源文件 4.3 PiecesList类 4.4 PuzzleWidget类 五.效果演示 一.项目介绍 本文介绍利用QDrag类实现拖拽拼图功能.左边是打散的图,拖动到右边进行复现,此外程序还支持手动拖入原图片. 二.项目基本配置 新建一个Qt案例,项目名称为“puzzle”,基类选择“QMainWindow”,取消选中创建

  • vue3使用自定义指令实现el dialog拖拽功能示例详解

    目录 实现el-dialog的拖拽功能 通过自定义指令实现拖拽功能 实现拖拽功能 使用方式 实现el-dialog的拖拽功能 这里指的是 element-plus 的el-dialog组件,一开始该组件并没有实现拖拽的功能,当然现在可以通过设置属性的方式实现拖拽. 自带的拖拽功能非常严谨,拖拽时判断是否拖拽出窗口,如果出去了会阻止拖拽. 如果自带的拖拽功能可以满足需求的话,可以跳过本文. 通过自定义指令实现拖拽功能 因为要自己操作dom(设置事件),所以感觉还是使用自定义指令更直接一些,而且对原

  • 基于javascript的拖拽类封装详解

    效果图如下 github地址如下: github地址 使用方法 引入js和对应的css import Drag from '../../static/dragger.js' import './assets/css/dragger.css' 之后,实例化 new Drag({ id: 'box-dragger', showAngle: true, isScale: false, showBorder: false }) new Drag({ id: 'box-dragger2', canZoom

  • JavaScript制作楼层导航效果流程详解

    目录 本期目标 1. 功能实现 1.1 结构层 1.2 样式层 1.3 行为层 1.3.1 楼层跳转 1.3.2 楼层监听 2. 效果预览 3. 项目代码 本期目标 使用JavaScript制作楼层导航效果,实现两个功能: 楼层跳转 楼层监听 1. 功能实现 1.1 结构层 <div id="box" class="box"> <ul class="list"> <li class="content-par

  • C++使用easyX库实现三星环绕效果流程详解

    目录 1,项目描述 2,解决思路 3,关键代码 4,项目运行截图 5,具体代码实现 1,项目描述 功能1:使用图形化的方式描述地球围绕着太阳转动,月球围绕着地球转动 功能2:在转动的过程中当用户按下1,2,3,4,5,6,7时它可以变换出7种不同的颜色,当用户按下8时它可以变换从1-7的颜色依次变换当用户再次按下8键时停止变换颜色 功能3:当用户按下上键时,地球会围绕太阳反转,当再次按下上键时地球会恢复到正转 功能4:当用户按下空格键的时候,所有动画暂停,当再次按下空格键的时候所有动画继续进行.

  • JS面向对象编程实现的拖拽功能案例详解

    本文实例讲述了JS面向对象编程实现的拖拽功能.分享给大家供大家参考,具体如下: 原始的面向过程代码: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <style> #box { width: 100px; height: 100px; background: blue; position: absolute; } </style> <title>

  • DragChartPanel可拖拽曲线应用详解

    DragChartPanel  是java cs架构中的一种图形展现的开源组件. 业务需求需要用到DragChartPanel  ,这是一种根据jtable表格中的数据给与展示的图形组件.它和其他图形组件区别再与它可以进行拖拽,用户通过它不仅可以看出数据变化的曲线,而且可以通过拖拽修改表格中的数据. 下面展示一下它的效果图: 丑归丑,但是很实用呀. 下面展示它的代码 初始化坐标格图: chartpanel1 = new DragChartPanel(this); chartpanel1.setX

  • React DnD如何处理拖拽详解

    目录 正文 代码结构 DndProvider DragDropManager useDrag HTML5Backend TouchBackend 总结 正文 React DnD 是一个专注于数据变更的 React 拖拽库,通俗的将,你拖拽改变的不是页面视图,而是数据.React DnD 不提供炫酷的拖动体验,而是通过帮助我们管理拖拽中的数据变化,再由我们根据这些数据进行渲染.我们可能需要写额外的视图层来完成想要的效果,但是这种拖拽管理方式非常的通用,可以在任何场景下使用.初次使用可能感觉并不是那

  • 基于React.js实现原生js拖拽效果引发的思考

    一.起因&思路 一直想写一个原生js拖拽效果,又加上近来学react学得比较嗨.所以就用react来实现这个拖拽效果. 首先,其实拖拽效果的思路是很简单的.主要就是三个步骤: 1.onmousedown的时候,启动可拖拽事件,记录被拖拽元素的原始坐标参数. 2.onmousemove的时候,实时记录鼠标移动的距离,结合被拖拽元素第一阶段的坐标参数,计算并设置新的坐标值. 3.onmouseup的时候,关闭可拖拽事件,记录新的坐标值. 注意:这里主要是通过绝对定位的top和left来确定元素的位置

随机推荐