react項(xiàng)目中使用react-dnd實(shí)現(xiàn)列表的拖拽排序功能
現(xiàn)在有一個(gè)新需求就是需要對(duì)一個(gè)列表,實(shí)現(xiàn)拖拽排序的功能,要實(shí)現(xiàn)的效果如下圖:
可以通過(guò) 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.先安裝依賴(lài)
npm i react-dnd
npm i react-dnd-html5-backend
2.創(chuàng)建一個(gè) index.js 文件
DndProvider 組件為您的應(yīng)用程序提供 React-DnD 功能。這必須通過(guò) backend 支柱注入后端,但也可以注入 window 物體。
backend
:必填。一個(gè)React DnD后端。除非您正在編寫(xiě)自定義的,否則您可能希望使用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)。返回類(lèi)似于{ type, id }
此方法的東西是個(gè)好主意。item.type
必須設(shè)置,它必須是字符串,ES6符號(hào)。只有為相同類(lèi)型注冊(cè)的放置目標(biāo)才會(huì)對(duì)此項(xiàng)目做出反應(yīng)。閱讀概述以了解有關(guān)項(xiàng)目和類(lèi)型的更多信息。previewOptions
: 可選的。描述拖動(dòng)預(yù)覽選項(xiàng)的純JavaScript對(duì)象。options
: 可選的。一個(gè)普通的對(duì)象。如果組件的某些道具不是標(biāo)量(即,不是原始值或函數(shù)),則arePropsEqual(props, otherProps)
在options
對(duì)象內(nèi)指定自定義函數(shù)可以提高性能。除非您遇到性能問(wèn)題,否則不要擔(dān)心。begin(monitor)
: 可選的。拖動(dòng)操作開(kāi)始時(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)通過(guò)從其方法返回普通對(duì)象來(lái)指定放置結(jié)果drop()
,則它將可用作monitor.getDropResult()
。此方法是觸發(fā)Flux動(dòng)作的好地方。注意:如果在拖動(dòng)時(shí)卸載組件,則component
參數(shù)設(shè)置為null
。canDrag(monitor)
: 可選的。用它來(lái)指定當(dāng)前是否允許拖動(dòng)。如果您想要始終允許它,只需省略此方法即可。如果您想基于某些謂詞禁用拖動(dòng),則指定它很方便props
。注意:您可能無(wú)法調(diào)用monitor.canDrag()
此方法。isDragging(monitor)
: 可選的。默認(rèn)情況下,僅啟動(dòng)拖動(dòng)操作的拖動(dòng)源被視為拖動(dòng)。您可以通過(guò)定義自定義isDragging
方法來(lái)覆蓋此行為。它可能會(huì)返回類(lèi)似的東西props.id === monitor.getItem().id
。如果在拖動(dòng)過(guò)程中可以卸載原始組件并在以后使用其他父級(jí)“復(fù)活”,則執(zhí)行此操作。例如,當(dāng)在卡片板中的列表中移動(dòng)卡時(shí),您希望它保留拖動(dòng)的外觀 - 即使在技術(shù)上,組件也會(huì)被卸載,并且每次將其移動(dòng)到另一個(gè)列表時(shí)都會(huì)安裝另一個(gè)組件。注意:您可能無(wú)法調(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ì)指定類(lèi)型的拖動(dòng)源生成的項(xiàng)目作出反應(yīng)。options
: 可選的。一個(gè)普通的對(duì)象。如果組件的某些道具不是標(biāo)量(即,不是原始值或函數(shù)),則arePropsEqual(props, otherProps)
在options
對(duì)象內(nèi)指定自定義函數(shù)可以提高性能。除非您遇到性能問(wèn)題,否則不要擔(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
通過(guò)檢查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)
: 可選的。使用它來(lái)指定放置目標(biāo)是否能夠接受該項(xiàng)目。如果您想要始終允許它,只需省略此方法即可。如果你想基于某個(gè)謂詞overprops
或者禁用刪除,那么指定它是很方便的monitor.getItem()
。注意:您可能無(wú)法調(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({ //定義拖拽的類(lèi)型 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)相同的話(huà),停止執(zhí)行 if (dragIndex === hoverIndex) { return } //如果不做以下處理,則卡片移動(dòng)到另一個(gè)卡片上就會(huì)進(jìn)行交換,下方處理使得卡片能夠在跨過(guò)中心線(xià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)無(wú)限交換情況 }, }) 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)前功能的,所以在開(kāi)發(fā)前請(qǐng)確認(rèn)react版本
到此這篇關(guān)于react項(xiàng)目中使用react-dnd實(shí)現(xiàn)列表的拖拽排序的文章就介紹到這了,更多相關(guān)react-dnd列表的拖拽排序內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React純前端模擬實(shí)現(xiàn)登錄鑒權(quán)
這篇文章主要為大家詳細(xì)介紹了React純前端模擬實(shí)現(xiàn)登錄鑒權(quán)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-04-04React學(xué)習(xí)筆記之高階組件應(yīng)用
這篇文章主要介紹了React 高階組件應(yīng)用,詳細(xì)的介紹了什么是React高階組件和具體使用,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06React?useEffect不支持async?function示例分析
這篇文章主要為大家介紹了React?useEffect不支持async?function示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07React Native自定義標(biāo)題欄組件的實(shí)現(xiàn)方法
今天講一下如何實(shí)現(xiàn)自定義標(biāo)題欄組件,我們都知道RN有一個(gè)優(yōu)點(diǎn)就是可以組件化,在需要使用該組件的地方直接引用并傳遞一些參數(shù)就可以了,這種方式確實(shí)提高了開(kāi)發(fā)效率。對(duì)React Native自定義標(biāo)題欄組件的實(shí)現(xiàn)方法感興趣的朋友參考下2017-01-01react koa rematch 如何打造一套服務(wù)端渲染架子
這篇文章主要介紹了react koa rematch 如何打造一套服務(wù)端渲染架子,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06react實(shí)現(xiàn)頁(yè)面水印效果的全過(guò)程
大家常常關(guān)注的是網(wǎng)站圖片增加水印,而很少關(guān)注頁(yè)面水印,其實(shí)這個(gè)需求也是比較常見(jiàn)的,比如公文系統(tǒng)、合同系統(tǒng)等,這篇文章主要給大家介紹了關(guān)于react實(shí)現(xiàn)頁(yè)面水印效果的相關(guān)資料,需要的朋友可以參考下2021-09-09一文詳解ReactNative狀態(tài)管理rematch使用
這篇文章主要為大家介紹了ReactNative狀態(tài)管理rematch使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03基于React編寫(xiě)一個(gè)全局Toast的示例代碼
前些日子在做項(xiàng)目的時(shí)候,需要封裝一個(gè)Toast組件,我想起之前用過(guò)的庫(kù),只要在入口文件中引入就可以在全局中使用,還是很方便的,借這次機(jī)會(huì)也來(lái)實(shí)現(xiàn)一下,所以本文介紹了React中如何編寫(xiě)一個(gè)全局Toast,需要的朋友可以參考下2024-05-05