react項(xiàng)目中使用react-dnd實(shí)現(xiàn)列表的拖拽排序

現(xiàn)在有一個(gè)新需求就是需要對(duì)一個(gè)列表,實(shí)現(xiàn)拖拽排序的功能,要實(shí)現(xiàn)的效果如下圖:?

可以通過 react-dnd 或者 react-beautiful-dnd 兩種方式實(shí)現(xiàn),今天先講下使用react-dnd是如何實(shí)現(xiàn)的,github地址:

https://react-dnd.github.io/react-dnd/docs/api/dnd-provider

1.先安裝依賴

npm i?react-dnd

npm i?react-dnd-html5-backend

2.創(chuàng)建一個(gè) index.js 文件

DndProvider組件為您的應(yīng)用程序提供 React-DnD 功能。這必須通過?backend 支柱注入后端,但也可以注入?window?物體。

backend:必填。一個(gè)React DnD后端。除非您正在編寫自定義的,否則您可能希望使用React DnD附帶的HTML5后端。

context: 可選的。用于配置后端的后端上下文。這取決于后端實(shí)現(xiàn)。

options: 可選的。用于配置后端的選項(xiàng)對(duì)象。這取決于后端實(shí)現(xiàn)。

import React from 'react'

import Example from './example'

import { DndProvider } from 'react-dnd'

import HTML5Backend from 'react-dnd-html5-backend'

class App extends React.Component{

? ? /**

? ? * 獲取排序后的新數(shù)據(jù)回調(diào)函數(shù)

? ? */

? ? handlePreviewList = (previewList) => {

? ? ? ? this.setState({

? ? ? ? ? ? previewList

? ? ? ? })

? ? }

return (

<div className="App">

<DndProvider backend={HTML5Backend}>

<Example previewList={previewList}

? ? ? ? ? ? ? ? ? ? handlePreviewList={this.handlePreviewList}/>

</DndProvider>

</div>

)

}

export default App;

3.新建example.js文件

previewList是 index.js組件傳入的數(shù)據(jù)。

handlePreviewList 是保存排序后的新數(shù)據(jù)。

import React, { useState } from 'react'

import TopicList from './TopicList';

const Container = ({ previewList, handlePreviewList }) => {

? ? {

? ? ? ? const [topic] = useState(previewList)

? ? ? ? const handleDND = (dragIndex, hoverIndex) => {

? ? ? ? ? ? let tmp = previewList[dragIndex] //臨時(shí)儲(chǔ)存文件

? ? ? ? ? ? previewList.splice(dragIndex, 1) //移除拖拽項(xiàng)

? ? ? ? ? ? previewList.splice(hoverIndex, 0, tmp) //插入放置項(xiàng)

? ? ? ? ? ? handlePreviewList(previewList)

? ? ? ? };

? ? ? ? const renderCard = (item, index) => {

? ? ? ? ? ? return (

? ? ? ? ? ? ? ? <TopicList

? ? ? ? ? ? ? ? ? ? key={item.questionTuid}

? ? ? ? ? ? ? ? ? ? index={index}

? ? ? ? ? ? ? ? ? ? id={item.questionTuid}

? ? ? ? ? ? ? ? ? ? text={item.questionContent}

? ? ? ? ? ? ? ? ? ? moveCard={handleDND}

? ? ? ? ? ? ? ? />

? ? ? ? ? ? )

? ? ? ? }

? ? ? ? return (

? ? ? ? ? ? <div>

? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? topic.map((item, i) => renderCard(item, i))

? ? ? ? ? ? ? ? }

? ? ? ? ? ? </div>

? ? ? ? )

? ? }

}

export default Container

4.新建TopicLis.js文件

useDrag?一個(gè)鉤子,用于將當(dāng)前組件用作拖動(dòng)源。

參數(shù)

spec規(guī)范對(duì)象

返回值數(shù)組

Index 0:包含collect函數(shù)中收集的屬性的對(duì)象。如果collect未定義任何函數(shù),則返回空對(duì)象。

Index 1:拖動(dòng)源的連接器功能。這必須附加到DOM的可拖動(dòng)部分。

Index 2:拖動(dòng)預(yù)覽的連接器功能。這可以附加到DOM的預(yù)覽部分。

規(guī)范對(duì)象成員

item:必填。一個(gè)簡(jiǎn)單的JavaScript對(duì)象,描述被拖動(dòng)的數(shù)據(jù)。這是關(guān)于拖動(dòng)源的放置目標(biāo)可用的唯一信息,因此選擇他們需要知道的最小數(shù)據(jù)非常重要。你可能想在這里放一個(gè)復(fù)雜的引用,但是你應(yīng)該盡量避免這樣做,因?yàn)樗詈狭送蟿?dòng)源和放下目標(biāo)。返回類似于{ type, id }此方法的東西是個(gè)好主意。

item.type必須設(shè)置,它必須是字符串,ES6符號(hào)。只有為相同類型注冊(cè)的放置目標(biāo)才會(huì)對(duì)此項(xiàng)目做出反應(yīng)。閱讀概述以了解有關(guān)項(xiàng)目和類型的更多信息。

previewOptions: 可選的。描述拖動(dòng)預(yù)覽選項(xiàng)的純JavaScript對(duì)象。

options: 可選的。一個(gè)普通的對(duì)象。如果組件的某些道具不是標(biāo)量(即,不是原始值或函數(shù)),則arePropsEqual(props, otherProps)在options對(duì)象內(nèi)指定自定義函數(shù)可以提高性能。除非您遇到性能問題,否則不要擔(dān)心。

begin(monitor): 可選的。拖動(dòng)操作開始時(shí)觸發(fā)。不需要返回任何內(nèi)容,但如果返回一個(gè)對(duì)象,它將覆蓋item規(guī)范的默認(rèn)屬性。

end(item, monitor): 可選的。當(dāng)拖動(dòng)停止時(shí),end被調(diào)用。對(duì)于每個(gè)begin呼叫,end保證相應(yīng)的呼叫。您可以調(diào)用monitor.didDrop()以檢查丟棄是否由兼容的放置目標(biāo)處理。如果它被處理,并且放置目標(biāo)通過從其方法返回普通對(duì)象來指定放置結(jié)果drop(),則它將可用作monitor.getDropResult()。此方法是觸發(fā)Flux動(dòng)作的好地方。注意:如果在拖動(dòng)時(shí)卸載組件,則component參數(shù)設(shè)置為null。

canDrag(monitor): 可選的。用它來指定當(dāng)前是否允許拖動(dòng)。如果您想要始終允許它,只需省略此方法即可。如果您想基于某些謂詞禁用拖動(dòng),則指定它很方便props。注意:您可能無法調(diào)用monitor.canDrag()此方法。

isDragging(monitor): 可選的。默認(rèn)情況下,僅啟動(dòng)拖動(dòng)操作的拖動(dòng)源被視為拖動(dòng)。您可以通過定義自定義isDragging方法來覆蓋此行為。它可能會(huì)返回類似的東西props.id === monitor.getItem().id。如果在拖動(dòng)過程中可以卸載原始組件并在以后使用其他父級(jí)“復(fù)活”,則執(zhí)行此操作。例如,當(dāng)在卡片板中的列表中移動(dòng)卡時(shí),您希望它保留拖動(dòng)的外觀 - 即使在技術(shù)上,組件也會(huì)被卸載,并且每次將其移動(dòng)到另一個(gè)列表時(shí)都會(huì)安裝另一個(gè)組件。注意:您可能無法調(diào)用monitor.isDragging()此方法。

collect: 可選的。收集功能。它應(yīng)該返回一個(gè)普通的道具對(duì)象返回注入你的組件。它接收兩個(gè)參數(shù),monitor和props。閱讀概述,了解監(jiān)視器和收集功能的介紹。請(qǐng)參閱下一節(jié)中詳細(xì)描述的收集功能。

useDrop?一個(gè)鉤子,用于將當(dāng)前組件用作放置目標(biāo)。

參數(shù)

spec規(guī)范對(duì)象

返回值數(shù)組

Index 0:包含collect函數(shù)中收集的屬性的對(duì)象。如果collect未定義任何函數(shù),則返回空對(duì)象。

Index 1:放置目標(biāo)的連接器功能。這必須附加到DOM的drop-target部分。

規(guī)范對(duì)象成員

accept:必填。一個(gè)字符串,一個(gè)ES6符號(hào),一個(gè)數(shù)組或一個(gè)返回給定組件的函數(shù)的函數(shù)props。此放置目標(biāo)僅對(duì)指定類型的拖動(dòng)源生成的項(xiàng)目作出反應(yīng)。

options: 可選的。一個(gè)普通的對(duì)象。如果組件的某些道具不是標(biāo)量(即,不是原始值或函數(shù)),則arePropsEqual(props, otherProps)在options對(duì)象內(nèi)指定自定義函數(shù)可以提高性能。除非您遇到性能問題,否則不要擔(dān)心。

drop(item, monitor): 可選的。在目標(biāo)上放置兼容項(xiàng)目時(shí)調(diào)用。您可以返回undefined或普通對(duì)象。如果返回一個(gè)對(duì)象,它將成為放置結(jié)果,并且可以在其endDrag方法中作為拖動(dòng)源使用monitor.getDropResult()。如果您希望根據(jù)接收到丟棄的目標(biāo)執(zhí)行不同的操作,這非常有用。如果您有嵌套的放置目標(biāo),則可以drop通過檢查monitor.didDrop()和測(cè)試是否已經(jīng)處理了嵌套目標(biāo)monitor.getDropResult()。此方法和源endDrag方法都是觸發(fā)Flux操作的好地方。如果canDrop()已定義并返回,則不會(huì)調(diào)用此方法false。

hover(item, monitor): 可選的。當(dāng)項(xiàng)目懸停在組件上時(shí)調(diào)用。您可以檢查monitor.isOver({ shallow: true })以測(cè)試懸停是發(fā)生在當(dāng)前目標(biāo)上還是嵌套上。與drop()此不同,即使canDrop()定義并返回,也會(huì)調(diào)用此方法false。您可以檢查monitor.canDrop()以測(cè)試是否是這種情況。

canDrop(item, monitor): 可選的。使用它來指定放置目標(biāo)是否能夠接受該項(xiàng)目。如果您想要始終允許它,只需省略此方法即可。如果你想基于某個(gè)謂詞overprops或者禁用刪除,那么指定它是很方便的monitor.getItem()。注意:您可能無法調(diào)用monitor.canDrop()此方法。

collect: 可選的。收集功能。它應(yīng)該返回一個(gè)普通的道具對(duì)象返回注入你的組件。它接收兩個(gè)參數(shù),monitor和props,了解監(jiān)視器和收集功能的介紹。請(qǐng)參閱下一節(jié)中詳細(xì)描述的收集功能。

import React, { useRef } from 'react'

import { useDrag, useDrop } from 'react-dnd'

import ItemTypes from './ItemTypes';

const style = {

? padding: '0.5rem 1rem',

? marginBottom: '.5rem',

? backgroundColor: 'white',

? cursor: 'move',

}

const TopicList = ({ id, text, index, moveCard }) => {

? const ref = useRef(null)

? const [, drop] = useDrop({

? ? //定義拖拽的類型

? ? accept: ItemTypes.TOPIC,? ?

? ? hover(item, monitor) {

? ? ? //異常處理判斷

? ? ? if (!ref.current) {

? ? ? ? return

? ? ? }

? ? ? //拖拽目標(biāo)的Index

? ? ? const dragIndex = item.index;

? ? ? //放置目標(biāo)Index

? ? ? const hoverIndex = index;

? ? ? // 如果拖拽目標(biāo)和放置目標(biāo)相同的話,停止執(zhí)行

? ? ? if (dragIndex === hoverIndex) {

? ? ? ? return

? ? ? }

? ? ? //如果不做以下處理,則卡片移動(dòng)到另一個(gè)卡片上就會(huì)進(jìn)行交換,下方處理使得卡片能夠在跨過中心線后進(jìn)行交換.

? ? ? //獲取卡片的邊框矩形

? ? ? const hoverBoundingRect = ref.current.getBoundingClientRect();? ?

? ? ? //獲取X軸中點(diǎn)

? ? ? const hoverMiddleY =

? ? ? ? (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;

? ? ? //獲取拖拽目標(biāo)偏移量

? ? ? const clientOffset = monitor.getClientOffset();?

? ? ? const hoverClientY = clientOffset.y - hoverBoundingRect.top

? ? ? // 從上往下放置

? ? ? if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {?

? ? ? ? return

? ? ? }

? ? ? // 從下往上放置

? ? ? if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {?

? ? ? ? return

? ? ? }

? ? ? moveCard(dragIndex, hoverIndex);? //調(diào)用方法完成交換

? ? ? item.index = hoverIndex;? //重新賦值index,否則會(huì)出現(xiàn)無限交換情況

? ? },

? })

? const [{ isDragging }, drag] = useDrag({

? ? item: { type: ItemTypes.TOPIC, id, index },

? ? collect: monitor => ({

? ? ? isDragging: monitor.isDragging(),

? ? }),

? })

? const opacity = isDragging ? 0 : 1

? drag(drop(ref))

? return (

? ? <div ref={ref} style={{ ...style, opacity }}>

? ? ? <span style={{ float: 'left' }}>{index + 1}.</span>

? ? ? <div className='stem' dangerouslySetInnerHTML={{ __html: text }}></div>

? ? </div>

? )

}

export default TopicList

5.新建 ItemTypes.js

export default {

? TOPIC: 'topic'

}

注意:react的版本需要是react16的新版本,否則會(huì)報(bào)如下圖所示的錯(cuò)誤,具體兼容到幾,未測(cè)試,但是之前的react 16.4.2是實(shí)現(xiàn)不了當(dāng)前功能的,所以在開發(fā)前請(qǐng)確認(rèn)react版本

?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容