返回

100 行代码,掌握 React 拖拽 Hooks 的实现思路

前端

使用 React 拖拽 Hooks 轻松实现拖放功能

随着 React 在 Web 开发中的广泛采用,对拖拽库的需求也日益增长。这些库简化了元素拖放功能的实现,无需编写复杂的代码。市面上有各种成熟的 React 拖拽库,但它们的体积和学习成本往往较高。

如果你需要快速上手 React 拖拽功能,且不想引入庞大的库,那么可以考虑自己实现一个简单的 React 拖拽 Hooks。Hooks 是 React 16.8 中引入的新特性,允许在函数组件中使用状态和生命周期方法。借助 Hooks,我们可以轻松实现一个拖拽 Hooks,而无需编写复杂的类组件。

实现思路

React 拖拽 Hooks 的实现思路很简单,主要包含以下步骤:

  • 创建 useDrag Hook: 监听元素的拖拽事件,并在拖拽的各个生命周期执行不同操作。
  • 创建 useDrop Hook: 监听元素的拖放事件,并在拖放的各个生命周期执行不同操作。
  • 在组件中使用这两个 Hook: 实现元素的拖拽功能。

代码实现

以下是 useDrag 和 useDrop Hook 的代码实现:

// useDrag Hook
import { useEffect, useRef } from 'react';

const useDrag = (ref, options) => {
  const dragStart = useRef(false);
  const dragData = useRef(null);
  const dragTarget = useRef(null);

  useEffect(() => {
    const element = ref.current;

    const handleDragStart = (e) => {
      dragStart.current = true;
      dragData.current = e.target.dataset.dragData;
      dragTarget.current = element;
    };

    const handleDragEnd = (e) => {
      dragStart.current = false;
      dragData.current = null;
      dragTarget.current = null;
    };

    const handleDragOver = (e) => {
      e.preventDefault();

      if (dragStart.current && dragTarget.current !== element) {
        options.onDragOver(e, dragData.current);
      }
    };

    const handleDrop = (e) => {
      e.preventDefault();

      if (dragStart.current && dragTarget.current !== element) {
        options.onDrop(e, dragData.current);
      }
    };

    element.addEventListener('dragstart', handleDragStart);
    element.addEventListener('dragend', handleDragEnd);
    element.addEventListener('dragover', handleDragOver);
    element.addEventListener('drop', handleDrop);

    return () => {
      element.removeEventListener('dragstart', handleDragStart);
      element.removeEventListener('dragend', handleDragEnd);
      element.removeEventListener('dragover', handleDragOver);
      element.removeEventListener('drop', handleDrop);
    };
  }, [ref, options]);
};

// useDrop Hook
const useDrop = (ref, options) => {
  const dragOver = useRef(false);

  useEffect(() => {
    const element = ref.current;

    const handleDragOver = (e) => {
      e.preventDefault();
      dragOver.current = true;
    };

    const handleDragLeave = (e) => {
      e.preventDefault();
      dragOver.current = false;
    };

    const handleDrop = (e) => {
      e.preventDefault();

      if (dragOver.current) {
        options.onDrop(e, e.dataTransfer.getData('text'));
      }
    };

    element.addEventListener('dragover', handleDragOver);
    element.addEventListener('dragleave', handleDragLeave);
    element.addEventListener('drop', handleDrop);

    return () => {
      element.removeEventListener('dragover', handleDragOver);
      element.removeEventListener('dragleave', handleDragLeave);
      element.removeEventListener('drop', handleDrop);
    };
  }, [ref, options]);
};

使用示例

以下代码演示了如何在组件中使用 useDrag 和 useDrop Hook:

import React, { useRef } from 'react';
import { useDrag, useDrop } from './useDragAndDropHooks';

const DraggableItem = ({ data }) => {
  const ref = useRef(null);

  useDrag(ref, {
    onDragStart: (e, dragData) => {
      e.dataTransfer.setData('text', dragData);
    },
    onDragOver: (e, dragData) => {
      console.log('Item is being dragged over another item');
    },
    onDrop: (e, dragData) => {
      console.log('Item was dropped on another item');
    },
  });

  return (
    <div ref={ref} data-drag-data={data}>
      {data}
    </div>
  );
};

const DroppableArea = () => {
  const ref = useRef(null);

  useDrop(ref, {
    onDrop: (e, data) => {
      console.log('Item was dropped in the droppable area');
    },
  });

  return (
    <div ref={ref}>
      Droppable Area
    </div>
  );
};

const App = () => {
  return (
    <div>
      <DraggableItem data="Item 1" />
      <DraggableItem data="Item 2" />
      <DraggableItem data="Item 3" />
      <DroppableArea />
    </div>
  );
};

export default App;

优点

使用 React 拖拽 Hooks 具有以下优点:

  • 轻量级: 无需引入庞大的库,避免了代码臃肿。
  • 易于使用: 只需要简单的 Hook 即可实现拖拽功能。
  • 灵活: 可以根据需要自定义拖拽行为。
  • 高效: Hooks 机制提供了高效的性能。

常见问题解答

  1. 我可以在 React Native 中使用这些 Hooks 吗?
    答:否,这些 Hooks 仅适用于 Web 端的 React。

  2. 是否可以控制拖拽时的光标样式?
    答:是的,可以在 onDragStart 事件处理函数中设置 e.dataTransfer.effectAllowed 属性。

  3. 如何防止拖拽到某些区域?
    答:可以在 useDrop Hook 中检查 e.target 并根据需要返回 false 以禁止拖放。

  4. 是否可以设置拖拽数据类型?
    答:是的,可以在 onDragStart 事件处理函数中使用 e.dataTransfer.setData 方法设置类型。

  5. 如何处理多元素拖拽?
    答:可以使用 HTML5 的 DragEvent.dataTransfer.items 属性来处理多个拖拽元素。