欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React中使用dnd-kit實現(xiàn)拖曳排序功能

 更新時間:2024年06月27日 09:09:28   作者:non_hana  
在這篇文章中,我將帶著大家一起探究React中使用dnd-kit實現(xiàn)拖曳排序功能,由于前陣子需要在開發(fā) Picals 的時候,需要實現(xiàn)一些拖動排序的功能,文中通過代碼示例介紹的非常詳細,需要的朋友可以參考下

由于前陣子需要在開發(fā) Picals 的時候,需要實現(xiàn)一些拖動排序的功能。雖然有原生的瀏覽器 dragger API,不過純靠自己手寫很難實現(xiàn)自己想要的效果,更多的是吃力不討好。于是我四處去調(diào)研了一些 React 中比較常用的拖曳庫,最終確定了 dnd-kit 作為我實現(xiàn)拖曳排序的工具。

當然,使用的時候肯定免不了踩坑。這篇文章的意義就是為了記錄所踩的坑,希望能夠為有需要的大家提供一點幫助。

在這篇文章中,我將帶著大家一起實現(xiàn)如下的拖曳排序的例子:

那讓我們開始吧。

安裝

安裝 dnd-kit 工具庫很簡單,只需要輸入下面的命令進行安裝即可:

pnpm add @dnd-kit/core @dnd-kit/sortable @dnd-kit/modifiers @dnd-kit/utilities

這幾個包分別有什么作用呢?

  • @dnd-kit/core:核心庫,提供基本的拖拽功能。
  • @dnd-kit/sortable:擴展庫,提供排序功能和工具。
  • @dnd-kit/modifiers:修飾庫,提供拖拽行為的限制和修飾功能。
  • @dnd-kit/utilities:工具庫,提供 CSS 和實用工具函數(shù)。上述演示的平滑移動的樣式就是來源于這個包。

使用方法

首先我們需要知道的是,拖曳這個行為需要涉及到兩個部分:

  • 能夠允許被拖曳的有限空間(父容器)
  • 用戶真正進行拖曳的子元素

在使用 dnd-kit 時,需要對這兩個部分分別進行定義。

父容器(DraggableList)的編寫

我們首先進行拖曳父容器相關(guān)的功能配置。話不多說我們直接上代碼:

import { FC, useEffect, useState } from "react";
import type { DragEndEvent, DragMoveEvent } from "@dnd-kit/core";
import { DndContext } from "@dnd-kit/core";
import {
  arrayMove,
  SortableContext,
  rectSortingStrategy,
} from "@dnd-kit/sortable";
import { restrictToParentElement } from "@dnd-kit/modifiers";
import "./index.scss";
import DraggableItem from "../draggable-item";

type ImgItem = {
  id: number;
  url: string;
};

const DraggableList: FC = () => {
  const [list, setList] = useState<ImgItem[]>([]);

  useEffect(() => {
    setList(
      Array.from({ length: 31 }, (_, index) => ({
        id: index + 1,
        url: String(index),
      }))
    );
  }, []);

  const getMoveIndex = (array: ImgItem[], dragItem: DragMoveEvent) => {
    const { active, over } = dragItem;
    const activeIndex = array.findIndex((item) => item.id === active.id);
    const overIndex = array.findIndex((item) => item.id === over?.id);

    // 處理未找到索引的情況
    return {
      activeIndex: activeIndex !== -1 ? activeIndex : 0,
      overIndex: overIndex !== -1 ? overIndex : activeIndex,
    };
  };

  const dragEndEvent = (dragItem: DragEndEvent) => {
    const { active, over } = dragItem;
    if (!active || !over) return; // 處理邊界情況

    const moveDataList = [...list];
    const { activeIndex, overIndex } = getMoveIndex(moveDataList, dragItem);

    if (activeIndex !== overIndex) {
      const newDataList = arrayMove(moveDataList, activeIndex, overIndex);
      setList(newDataList);
    }
  };

  return (
    <DndContext onDragEnd={dragEndEvent} modifiers={[restrictToParentElement]}>
      <SortableContext
        items={list.map((item) => item.id)}
        strategy={rectSortingStrategy}
      >
        <div className="drag-container">
          {list.map((item) => (
            <DraggableItem key={item.id} item={item} />
          ))}
        </div>
      </SortableContext>
    </DndContext>
  );
};

export default DraggableList;

對應(yīng)的 index.scss

.drag-container {
  position: relative;
  width: 800px;
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
}

return 的 DOM 元素結(jié)構(gòu)非常簡單,最主要的無外乎兩個上下文組件:DndContextSortableContext。

  • DndContext:是 dnd-kit 的核心組件,用于提供拖放的上下文。
  • SortableContext:是一個上下文組件,用于提供排序的功能。

SortableContext 組件內(nèi)部包裹的,就是我們正常的需要進行排序的列表容器了。當然,dnd-kit 也不是對任何的內(nèi)容都可以進行排序的。要想實現(xiàn)排序功能,這個被包裹的 DOM 元素必須符合以下幾個要求:

  • 必須是可排序的元素:SortableContext 需要包裹的元素具有相同的父級容器,且這些元素需要具備可排序的能力。每個子元素應(yīng)當是獨立的可拖拽項,例如一個列表項、卡片或網(wǎng)格中的塊。

  • 提供唯一的 id:每個可排序的子元素必須具有唯一的 id。SortableContext 會通過這些 id 來識別和管理每個拖拽項的位置。你需要確保 items 屬性中提供的 id 數(shù)組與實際渲染的子元素的 id 一一對應(yīng)。

  • 需要是同一個父容器的直接子元素:SortableContext 內(nèi)部的子元素必須是同一個父容器的直接子元素,不能有其他中間層級。這是因為排序和拖拽是基于元素的相對位置和布局來計算的。

  • 使用相同的布局策略:SortableContext 的子元素應(yīng)當使用相同的布局策略,例如使用 CSS Flexbox 或 Grid 進行布局。這樣可以確保拖拽操作時,子元素之間的排列和移動邏輯一致。

  • 設(shè)置相同的樣式屬性:確保子元素具有相同的樣式屬性,例如寬度、高度、邊距等。這些屬性一致性有助于拖拽過程中視覺效果的一致性和準確性。

  • 添加必要的樣式以支持拖拽:為了支持拖拽效果,子元素應(yīng)具備必要的樣式。例如,設(shè)置 positionrelative 以便于絕對定位的拖拽項,設(shè)置 overflow 以防止拖拽項溢出。

  • 確保有足夠的拖拽空間:父容器應(yīng)當有足夠的空間來允許子元素的拖拽操作。如果空間不足,可能會導致拖拽操作不順暢或無法完成。

  • 子元素必須具備 draggable 屬性:每個子元素應(yīng)該具備 draggable 屬性,以表明該元素是可拖動的。這通常通過 dnd-kit 提供的組件如 DraggableSortable 來實現(xiàn)。

  • 提供合適的拖拽處理程序:為子元素添加合適的拖拽處理程序,通常通過 dnd-kit 提供的鉤子或組件實現(xiàn)。例如,使用 useDraggable 鉤子來處理拖拽邏輯。

  • 處理子元素布局變化:確保在拖拽過程中,子元素的布局變化能夠被正確處理。例如,設(shè)置適當?shù)膭赢嬓Ч云交馗虏季帧?/p>

在這里附加一個說明,可以看到我初始化的數(shù)據(jù)的列表 id 是從 1 開始的,因為 從 0 開始會導致第一個元素無法觸發(fā)移動 ?,F(xiàn)階段還不知道是什么原因,大概的猜測是在 JavaScript 和 React 中,id0 可能會被視為“假值”(falsy value)。許多庫和框架在處理數(shù)據(jù)時,會有意無意地忽略或處理“假值”。dnd-kit 可能在某些情況下忽略了 id0 的元素,導致其無法正常參與拖曳操作??傊?, 避免第一個拖曳元素的 id 不要為 0 或者空字符串 。

對于 DndContext,需要傳入幾個 props 以處理拖曳事件本身。在這里,傳入了 onDragEnd 函數(shù)與 modifiers 修飾符列表。實際上,這個上下文組件能夠傳入很多的 props,我在這里簡單截個圖:

可以看到,不僅是結(jié)束回調(diào),也接受拖曳全過程的函數(shù)回調(diào)并通過回傳值進行一些數(shù)據(jù)處理。

但是,一般用于完成拖曳排序功能我們可以不管這么多,只用管鼠標松開后的回調(diào)函數(shù),然后拿到對象進行處理就可以了。

  • onDragEnd:顧名思義,就是用戶鼠標松開后觸發(fā)的拖曳事件的回調(diào)。觸發(fā)時會自動傳入類型為 DragEndEvent 的對象,我們可以從其中拿出 activeover 兩個參數(shù)來具體處理拖曳事件。

    active 包含 正在拖曳的元素的相關(guān)信息,over 包含最后鼠標松開時所覆蓋到的元素的相關(guān)信息。

    結(jié)合到我的函數(shù):

const dragEndEvent = (dragItem: DragEndEvent) => {
  const { active, over } = dragItem;
  if (!active || !over) return; // 處理邊界情況

  const moveDataList = [...list];
  const { activeIndex, overIndex } = getMoveIndex(moveDataList, dragItem);

  if (activeIndex !== overIndex) {
    const newDataList = arrayMove(moveDataList, activeIndex, overIndex);
    setList(newDataList);
  }
};

首先檢查 activeover 是否有效,避免邊界問題,之后創(chuàng)建 moveDataList 的副本,調(diào)用 getMoveIndex 函數(shù)獲取 activeover 項目的索引,如果兩個索引不同,使用 arrayMove 移動項目,并更新 list 狀態(tài)。

getMoveIndex 函數(shù)如下,用于獲取拖拽項目和目標位置的索引:

const getMoveIndex = (array: ImgItem[], dragItem: DragMoveEvent) => {
  const { active, over } = dragItem;
  const activeIndex = array.findIndex((item) => item.id === active.id);
  const overIndex = array.findIndex((item) => item.id === over?.id);

  // 處理未找到索引的情況
  return {
    activeIndex: activeIndex !== -1 ? activeIndex : 0,
    overIndex: overIndex !== -1 ? overIndex : activeIndex,
  };
};
  • 通過 findIndex 獲取 activeover 項目的索引,如果未找到,默認返回 0。

  • modifiers:標識符,傳入一個標識符數(shù)組以限制在父組件進行拖曳的行為。主要可選的一些標識符如下:

    • restrictToParentElement:限制在父元素內(nèi)。
    • restrictToFirstScrollableAncestor:限制在第一個可滾動祖先元素。
    • restrictToVerticalAxis:限制在垂直軸上。
    • restrictToHorizontalAxis:限制在水平軸上。
    • restrictToBoundingRect:限制在指定矩形區(qū)域內(nèi)。
    • snapCenterToCursor:使元素中心對齊到光標。

    在這里我選擇了一個比較普通的限制在父元素內(nèi)的標識符??梢园凑站唧w的定制需要,配置不同的標識符組合來限制拖曳行為。

接下來是對 SortableContext 的配置解析。在這個組件中傳入了 itemsstrategy 兩個參數(shù)。同樣地,它也提供了很多的 props 以供個性化配置:

items:用于定義可排序項目的唯一標識符數(shù)組,它告訴 SortableContext 哪些項目可以被拖拽和排序。它的類型剛好和上述的 active 和 over 的 id 屬性的類型相同,都是 UniqueIdentifier。

這也就意味著,我們在 items 這邊傳入了什么數(shù)組來對排序列表進行唯一性表示,active 和 over 就按照什么來追蹤元素的排序索引。UniqueIdentifier 實際上是 string 和 number 的聯(lián)合類型。

  • 因此,只要是每個 item 唯一的,無論傳字符串或者數(shù)字都是可以的。

  • strategy:策略,用于定義排序算法,它指定了拖拽項目在容器內(nèi)如何排序和移動。它通過提供一個函數(shù)來控制項目在拖拽過程中的排序行為。它決定了拖拽項目的排序方式和在拖拽過程中如何移動。例如,它可以控制項目按行、按列或者自由布局進行排序,并且不同的排序策略可以提供不同的用戶交互體驗。例如,矩形排序、水平排序或者垂直排序等。

    常用的排序策略有如下幾種:

    • rectSortingStrategy

      • 適用場景:矩形網(wǎng)格布局,比如 flex 容器內(nèi)部配置 flex-wrap: wrap 換行之后,可以采用這種策略。

      • 說明:項目根據(jù)矩形區(qū)域進行排序,適用于二維網(wǎng)格布局。

    • horizontalListSortingStrategy

      • 適用場景:水平列表,只用于單行的 flex 布局。

      • 說明:項目按水平順序排列,適用于水平滾動的列表。

    • verticalListSortingStrategy

      • 適用場景:垂直列表,只用于單列的 flex 布局,配置了 flex-direction: column 之后使用。

      • 說明:項目按垂直順序排列,適用于垂直滾動的列表。

    除了這幾種以外,你還可以自定義一些策略,按照你自己的需求自己寫。不過一般也用不到自己寫 www

至此,父容器組件介紹完畢,我們來看子元素怎么寫吧。

子元素(Draggable-item)的編寫

上代碼:

import { FC } from "react";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import "./index.scss";

type ImgItem = {
  id: number;
  url: string;
};

type DraggableItemProps = {
  item: ImgItem;
};

const DraggableItem: FC<DraggableItemProps> = ({ item }) => {
  const { setNodeRef, attributes, listeners, transform, transition } =
    useSortable({
      id: item.id,
      transition: {
        duration: 500,
        easing: "cubic-bezier(0.25, 1, 0.5, 1)",
      },
    });
  const styles = {
    transform: CSS.Transform.toString(transform),
    transition,
  };

  return (
    <div
      ref={setNodeRef}
      {...attributes}
      {...listeners}
      style={styles}
      className="draggable-item"
    >
      <span>{item.url}</span>
    </div>
  );
};

export default DraggableItem;

對應(yīng)的 index.scss

.draggable-item {
  width: 144px;
  height: 144px;
  background-color: #f0f0f0;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: large;
  cursor: pointer;
  user-select: none;
  border-radius: 10px;
  overflow: hidden;
}

子元素的編寫相較于父容器要簡單得多,需要手動配置的少,引入的包更多了。

首先是引入了 useSortable 這個 hook,主要用來啟用子元素的排序功能。這個鉤子返回了一組現(xiàn)成的屬性和方法:

  • setNodeRef:用于將 DOM 節(jié)點與拖拽行為關(guān)聯(lián)。
  • attributes:包含與可拖拽項目相關(guān)的屬性,例如 roletabIndex。
  • listeners:包含拖拽操作的事件監(jiān)聽器,例如 onMouseDown、onTouchStart。
  • transform:包含當前項目的轉(zhuǎn)換屬性,用于設(shè)置位置和旋轉(zhuǎn)等。
  • transition:定義項目的過渡效果,用于動畫處理。

它接受一個配置對象,其中包含了:

  • id:在父容器組件中提到的唯一標識符,需要和父容器中傳入 items 的列表的元素的屬性是一致的,一般直接通過 map 來一次性傳入。
  • transition:動畫效果的配置,包含 durationeasing。

之后我們定義了拖曳樣式 styles ,使用了 @dnd-kit/utilities 提供的 CSS 工具庫,用于處理 CSS 相關(guān)的樣式轉(zhuǎn)換,因為這里的 transform 是從 hook 拿到的,是其自定義的 Transform 類型,需要借助其轉(zhuǎn)為正常的 css 樣式。我們傳入了從 useSortable 中拿到的 transformtransition,用于處理拖曳 item 的樣式。

之后就是直接一股腦的將配置全部傳入要真正進行拖曳的 DOM 元素:

  return (
    <div
      ref={setNodeRef}
      {...attributes}
      {...listeners}
      style={styles}
      className="draggable-item"
    >
      <span>{item.url}</span>
    </div>
  );
};
  • ref={setNodeRef}:通過 setNodeRefdiv 關(guān)聯(lián)到拖拽功能。
  • {...attributes}:將所有與可拖拽項目相關(guān)的屬性應(yīng)用到 div,例如 role="button"tabIndex="0"。
  • {...listeners}:將所有拖拽操作的事件監(jiān)聽器應(yīng)用到 div,例如 onMouseDownonTouchStart,使其能夠響應(yīng)用戶的拖拽操作。這里是因為我整個 DOM 元素都要支持拖曳,所以我把它直接加到了最外層。如果需要只在子元素特定的區(qū)域內(nèi)實現(xiàn)拖曳,listeners 就加到需要真正鼠標拖動的那個 DOM 上即可。
  • style={styles}:應(yīng)用定義好的 styles 對象,設(shè)置 transformtransition 樣式,使拖拽時能夠?qū)崿F(xiàn)平滑過渡。
  • className="draggable-item":設(shè)置組件的樣式類名,用于樣式定義。

實現(xiàn)效果

父容器和子元素全都編寫完畢后,我們可以觀察一下總體的實現(xiàn)效果如何:

可以看到,元素已經(jīng)能夠正常地被排序,而且列表也能夠同樣地被更新。結(jié)合到具體的例子,可以把這個列表 item 結(jié)合更加復(fù)雜的類型進行處理即可。只要保證每個 item 有唯一的 id 即可。

對于原有點擊事件失效的處理

對于某些需要觸發(fā)點擊事件的拖曳 item,如果按照上述方式封裝了拖曳子元素所需的一些配置,那么 原有的點擊事件將會失效,因為原有的鼠標按下的點擊事件被拖曳事件給覆蓋掉了。當然,dnd-kit 肯定也是考慮到了這種情況。他們在其核心庫 @dnd-kit/core 當中封裝了一個 hook useSensors,用來配置 鼠標拖動多少個像素之后才觸發(fā)拖曳事件,在此之前不觸發(fā)拖曳事件

使用方法也非常簡單,首先從核心庫中導入這個 hook,之后進行如下的配置:

//拖拽傳感器,在移動像素5px范圍內(nèi),不觸發(fā)拖拽事件
const sensors = useSensors(
  useSensor(MouseSensor, {
    activationConstraint: {
      distance: 5,
    },
  })
);

這里配置了在 5px 范圍內(nèi)不觸發(fā)拖曳事件,這樣就可以在這個范圍內(nèi)進行點擊事件的正常觸發(fā)了。

在上面的 DndContext 的 props 中,我們也看到了其提供了這一屬性的配置。我們只用將編寫好的 sensors 傳入即可:

<DndContext onDragEnd={dragEndEvent} modifiers={[restrictToParentElement]}>
  <SortableContext
    items={list.map((item) => item.id)}
    strategy={rectSortingStrategy}
    sensors={sensors}
  >
    <div className="drag-container">
      {list.map((item) => (
        <DraggableItem key={item.id} item={item} />
      ))}
    </div>
  </SortableContext>
</DndContext>

以上就是React中使用dnd-kit實現(xiàn)拖曳排序功能的詳細內(nèi)容,更多關(guān)于React dnd-kit拖曳排序的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • React八大常見錯誤及其解決方案

    React八大常見錯誤及其解決方案

    這篇文章主要介紹了React八大常見錯誤及其解決方案,并通過代碼示例介紹的非常詳細,對大家學習react有一定的幫助,感興趣的朋友可以參考下
    2024-03-03
  • react16.8.0以上MobX在hook中的使用方法詳解

    react16.8.0以上MobX在hook中的使用方法詳解

    這篇文章主要為大家介紹了react16.8.0以上MobX在hook中的使用方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • react國際化化插件react-i18n-auto使用詳解

    react國際化化插件react-i18n-auto使用詳解

    這篇文章主要介紹了react國際化化插件react-i18n-auto使用詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-03-03
  • React虛擬列表的實現(xiàn)

    React虛擬列表的實現(xiàn)

    在開發(fā)過程中,總是遇到很多列表的顯示。當上數(shù)量級別的列表渲染于瀏覽器,終會導致瀏覽器的性能下降,你可以選擇其他方式避免,本文就介紹了虛擬列表來解決這個問題
    2021-05-05
  • React?Hooks--useEffect代替常用生命周期函數(shù)方式

    React?Hooks--useEffect代替常用生命周期函數(shù)方式

    這篇文章主要介紹了React?Hooks--useEffect代替常用生命周期函數(shù)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • React中Context的用法總結(jié)

    React中Context的用法總結(jié)

    Context?提供了一種在組件樹中共享數(shù)據(jù)的方式,而不必通過?props?顯式地逐層傳遞,主要用于共享那些對于組件樹中許多組件來說是"全局"的數(shù)據(jù),下面小編就來和大家簡單講講Context的用法吧
    2025-01-01
  • react項目如何運行在微信公眾號

    react項目如何運行在微信公眾號

    這篇文章主要介紹了react項目如何運行在微信公眾號,幫助大家更好的理解和學習使用react,感興趣的朋友可以了解下
    2021-04-04
  • React不能將useMemo設(shè)置為默認方法原因詳解

    React不能將useMemo設(shè)置為默認方法原因詳解

    這篇文章主要為大家介紹了React不能將useMemo設(shè)置為默認方法原因詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪<BR>
    2022-07-07
  • 從頭寫React-like框架的工程搭建實現(xiàn)

    從頭寫React-like框架的工程搭建實現(xiàn)

    這篇文章主要介紹了從頭寫React-like框架的工程搭建實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-04-04
  • React受控組件與非受控組件實例分析講解

    React受控組件與非受控組件實例分析講解

    具體來說這是一種react非受控組件,其狀態(tài)是在input的react內(nèi)部控制,不受調(diào)用者控制??梢允褂檬芸亟M件來實現(xiàn)。下面就說說這個React中的受控組件與非受控組件的相關(guān)知識,感興趣的朋友一起看看吧
    2023-01-01

最新評論