在公司初學react,其中一個要求讓我實現拖拽排序的功能,完成以後記錄一下實現方法,採用antd和reactDND來實現這個功能。css
首先,使用 create-react-app
腳手架建立一個最基本的react項目。html
npm install -g create-react-app create-react-app my-app cd my-app
OK,構建好了react項目,而後咱們引入antd
,和react-dnd
前端
$ yarn add antd $ yarn add react-dnd $ yarn add react-dnd-html5-backend
引用完antd後能夠按照antd官網上的方法完成按需加載。html5
咱們先使用antd寫出一個簡單的卡片列表,修改項目目錄的APP.js和App.css文件,新建一個文件CardItem.jsreact
//App.js import React, { Component } from 'react'; import CardItem from './CardItem' import './App.css'; const CardList = [{ //定義卡片內容 title:"first Card", id:1, content:"this is first Card" },{ title:"second Card", id:2, content:"this is second Card" },{ title:"Third Card", id:3, content:"this is Third Card" } ]; class App extends Component { state = { CardList }; render() { return ( <div className='card'> {CardList.map((item,index) => { return( <CardItem //向次級界面傳遞參數 key={item.id} title={item.title} content={item.content} index={index} /> ) })} </div> ); } } export default App; //App.css .card{ display: flex; margin: 50px; } .card div{ margin-right: 20px; } //CardItem.js import React, { Component } from 'react'; import {Card} from 'antd' class CardItem extends Component{ render(){ return( <div> <Card title={this.props.title} style={{ width: 300 }} > <p>{this.props.content}</p> </Card> </div> ) } } export default CardItem
好了,卡片編寫完成了,如今運行一下咱們的項目,看一下效果git
$ npm start or yarn start
OK,編寫完成,咱們如今要作的就是使用react-dnd
完成卡片的拖拽排序,使得firstCard,secondCard,thirdCard能夠隨意的交換。github
react-dnd
中提供了DragDropContext
,DragSource
,DropTarget
3種API;npm
DragSource
和 DropTarget
都須要包裹在DragDropContex
內理解了這些API的做用,一個卡片排序的構建思路大致就浮現出來了,怎麼樣實現一個卡片排序,其實很簡單,就是把卡片列表中的每個卡片都設置爲DropTarget
和DragSource
,最後在拖拽結束的時候進行卡片之間的重排序,完成這一功能的實現。下面咱們就來一步一步的實現它。segmentfault
首先設定DragDropContext
,在App.js
中引入 react-dnd
和react-dnd-html5-backend
(先npm install
這個插件)antd
//App.js import React, { Component } from 'react'; import CardItem from './CardItem' + import {DragDropContext} from 'react-dnd' + import HTML5Backend from 'react-dnd-html5-backend' import './App.css'; /*.. ..*/ - export default App; + export default DragDropContext(HTML5Backend)(App);
好了,如今被App.js
所包裹的子組件均可以使用DropTarget
和DragSource
了,咱們如今在子組件CardItem
中設定react-dnd
使得卡片如今可以有拖動的效果。
//CardItem.js import React, { Component } from 'react'; import {Card} from 'antd' + import { //引入react-dnd DragSource, DropTarget, } from 'react-dnd' const Types = { // 設定類型,只有DragSource和DropTarget的類型相同時,才能完成拖拽和放置 CARD: 'CARD' }; //DragSource相關設定 const CardSource = { //設定DragSource的拖拽事件方法 beginDrag(props,monitor,component){ //拖拽開始時觸發的事件,必須,返回props相關對象 return { index:props.index } }, endDrag(props, monitor, component){ //拖拽結束時的事件,可選 }, canDrag(props, monitor){ //是否能夠拖拽的事件。可選 }, isDragging(props, monitor){ // 拖拽時觸發的事件,可選 } }; function collect(connect,monitor) { //經過這個函數能夠經過this.props獲取這個函數所返回的全部屬性 return{ connectDragSource:connect.dragSource(), isDragging:monitor.isDragging() } } //DropTarget相關設定 const CardTarget = { drop(props, monitor, component){ //組件放下時觸發的事件 //... }, canDrop(props,monitor){ //組件能夠被放置時觸發的事件,可選 //... }, hover(props,monitor,component){ //組件在target上方時觸發的事件,可選 //... }, }; function collect1(connect,monitor) {//同DragSource的collect函數 return{ connectDropTarget:connect.dropTarget(), isOver:monitor.isOver(), //source是否在Target上方 isOverCurrent: monitor.isOver({ shallow: true }), canDrop: monitor.canDrop(),//可否被放置 itemType: monitor.getItemType(),//獲取拖拽組件type } } class CardItem extends Component{ render(){ const { isDragging, connectDragSource, connectDropTarget} = this.props; let opacity = isDragging ? 0.1 : 1; //當被拖拽時呈現透明效果 return connectDragSource( //使用DragSource 和 DropTarget connectDropTarget( <div> <Card title={this.props.title} style={{ width: 300 ,opacity}} > <p>{this.props.content}</p> </Card> </div> ) ) } } // 使組件鏈接DragSource和DropTarget let flow = require('lodash.flow'); export default flow( DragSource(Types.CARD,CardSource,collect), DropTarget(Types.CARD,CardTarget,collect1) )(CardItem)
最後這個鏈接方法我參考了 reactDND官網 的說明,你能夠去 lodash.flow的官網 進行查看並下載。
固然你也能夠選擇構造器的方法進行引用,如@DragSource(type, spec, collect)
和@DropTarget(types, spec, collect)
.
Even if you don't plan to use decorators, the partial application can
still be handy, because you can combine several DragSource and
DropTarget declarations in JavaScript using a functional composition
helper such as _.flow. With decorators, you can just stack the
decorators to achieve the same effect.
import { DragSource, DropTarget } from 'react-dnd'; import flow from 'lodash/flow'; class YourComponent { render() { const { connectDragSource, connectDropTarget } = this.props return connectDragSource(connectDropTarget( /* ... */ )) } } export default flow( DragSource(/* ... */), DropTarget(/* ... */) )(YourComponent);
如今咱們已經完成了一個拖拽效果的實現,如今咱們來看一下效果
能夠很明顯的看到拖拽帶來的效果,接下來咱們要完成拖拽放置後的排序函數。
咱們將排序函數放在App.js
當中,在CardItem.js
中的CardTarget
構造方法中的hover
函數中進行調用,接下來看具體的實現方法.
//CardItem.js const CardTarget = { hover(props,monitor,component){ if(!component) return null; //異常處理判斷 const dragIndex = monitor.getItem().index;//拖拽目標的Index const hoverIndex = props.index; //放置目標Index if(dragIndex === hoverIndex) return null;// 若是拖拽目標和放置目標相同的話,中止執行 //若是不作如下處理,則卡片移動到另外一個卡片上就會進行交換,下方處理使得卡片可以在跨過中心線後進行交換. const hoverBoundingRect = (findDOMNode(component)).getBoundingClientRect();//獲取卡片的邊框矩形 const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2;//獲取X軸中點 const clientOffset = monitor.getClientOffset();//獲取拖拽目標偏移量 const hoverClientX = (clientOffset).x - hoverBoundingRect.left; if (dragIndex < hoverIndex && hoverClientX < hoverMiddleX) { // 從前日後放置 return null } if (dragIndex > hoverIndex && hoverClientX > hoverMiddleX) { // 從後往前放置 return null } props.DND(dragIndex,hoverIndex); //調用App.js中方法完成交換 monitor.getItem().index = hoverIndex; //從新賦值index,不然會出現無限交換狀況 } }
//App.js handleDND = (dragIndex,hoverIndex) => { let CardList = this.state.CardList; let tmp = CardList[dragIndex] //臨時儲存文件 CardList.splice(dragIndex,1) //移除拖拽項 CardList.splice(hoverIndex,0,tmp) //插入放置項 this.setState({ CardList }) }; /* ... */ //添加傳遞參數傳遞函數 <CardItem //向次級界面傳遞參數 key={item.id} title={item.title} content={item.content} index={index} onDND={this.handleDND} />
好了,如今咱們已經完成了一個卡片排序功能的小demo,讓咱們來看一下效果吧!
本人初學前端不久,剛接觸react相關,這篇文章也是用於記錄一下本身工做時用到的一些小功能,本文參考了
強大的拖拽組件:React DnD 的使用
和 reactDND 官網上的相關例子,一些更復雜的狀況你們也能夠去reactDND的官網上查看.