本文詳細講解了 react-dnd 的 API 以及用法,並且附上了可供參考的 Demo,希望能夠給需要的朋友提供一下幫助。
一、概念
React DnD 是一組 React 高階組件,使用的時候只需要使用對應的 API 將目標組件進行包裹,即可實現拖動或接受拖動元素的功能。將拖動的事件轉換成對象中對應狀態的形式,不需要開發者自己判斷拖動狀態,只需要在傳入的 spec 對象中各個狀態屬性中做對應處理即可。剛剛接觸可能難以理解,真正熟悉用法之后會感覺很方便。
本文
Demo地址:react-dnd-dustbin。如有幫助,歡迎 Star。
二、DragSource:使組件能夠被拖拽
使用
DragSource包裹住組件,使其可以進行拖動。
使用方式
-  
          import React, { Component } from 'react';
-  
          import { DragSource } from 'react-dnd';
-  
          
-  
          const spec = {
-  
          beginDrag(props, monitor, component) {
-  
          // 這里 return 出去的對象屬性自行選擇,這里只是用 id 作為演示
-  
          return { id: props.id }
-  
          }
-  
          
-  
          endDrag(props, monitor, component) {
-  
          ...
-  
          }
-  
          
-  
          canDrag(props, monitor) {
-  
          ...
-  
          }
-  
          
-  
          isDragging(props, monitor) {
-  
          ...
-  
          }
-  
          }
-  
          
-  
          const collect = (connect, monitor) => ({
-  
          // 這里返回一個對象,會將對象的屬性都賦到組件的 props 中去。這些屬性需要自己定義。
-  
          connectDropTarget: connect.dropTarget(),
-  
          id: monitor.getItem().id
-  
          })
-  
          
-  
          
-  
          class MyComponent extends Component {
-  
          /* ... */
-  
          }
-  
          
-  
          export default MyComponent;
-  
          復制代碼
參數講解:
- type: 必填。字符串,ES6符號或返回給定組件的函數props。只有為相同類型注冊的 drop targets才會對此拖動源生成的項目做出反應
- spec:必填。一個普通的JavaScript對象,上面有一些允許的方法。它描述了拖動源如何對拖放事件做出反應。
- collect:必填。收集功能。它應該返回一個普通的對象注入你的組件。它接收兩個參數:connect和monitor。
- options:可選的。一個普通的對象。
spec 對象中的方法
-  beginDrag(props, monitor, component):必填。當拖動開始時,beginDrag被調用。您必須返回描述被拖動數據的純JavaScript對象。您返回的內容會被放置到monitor.getItem()獲取到的對象中。
-  endDrag(props, monitor, component):可選的。當拖動停止時,endDrag被調用。對於每個beginDrag,endDrag都會對應。
-  canDrag(props, monitor): 可選的。用它來指定當前是否允許拖動。如果您想要始終允許它,只需省略此方法即可。注意:您可能無法調用monitor.canDrag()此方法。
-  isDragging(props, monitor): 可選的。默認情況下,僅啟動拖動操作的拖動源被視為拖動。注意:您可能無法調用monitor.isDragging()此方法。
方法中的參數 props, monitor, component
- props:當前組件的- props
- monitor:一個- DragSourceMonitor實例。使用它來查詢有關當前拖動狀態的信息,例如當前拖動的項目及其類型,當前和初始坐標和偏移,以及它是否已被刪除。
- component:指定時,它是組件的實例。使用它來訪問底層DOM節點以進行位置或大小測量,或調用- setState以及其他組件方法。- isDragging、- canDrag方法里獲取不到- component這個參數,因為它們被調用時實例可能不可用
collect 中的 connect 和 monitor 參數
-  connect: 一個DragSourceConnector實例。它有兩種方法:dragPreview()和dragSource()。- dragSource() => (elementOrNode, options?):常用方法,返回一個函數,傳遞給組件用來將 source DOM 和 React DnD Backend 連接起來 
            - dragPreview():返回一個函數,傳遞給組件用來將拖動時預覽的 DOM 節點 和 React DnD Backend 連接起來
 
 
- dragSource() => (elementOrNode, options?):常用方法,返回一個函數,傳遞給組件用來將 source DOM 和 React DnD Backend 連接起來 
            
-  monitor:一個 DragSourceMonitor實例。包含下面各種方法:
| 方法 | 含義 | 
|---|---|
| canDrag() | 是否可以被拖拽。如果沒有正在進行拖動操作,則返回 true | 
| isDragging() | 是否正在被拖動。如果正在進行拖動操作,則返回 true | 
| getItemType() | 返回標識當前拖動項的類型的字符串或ES6符號。 如果沒有拖動項目,則返回 null | 
| getItem() | 返回表示當前拖動項的普通對象。 每個拖動源都必須通過從其beginDrag()方法返回一個對象來指定它。 如果沒有拖動項目,則返回 null | 
| getDropResult() | 返回表示最后記錄的放置 drop result對象 | 
| didDrop() | 如果某個 drop target處理了drop事件,則返回 true,否則返回 false。即使target沒有返回drop結果,didDrop()也會返回true。 在endDrag()中使用它來測試任何放置目標是否已處理掉落。 如果在endDrag()之外調用,則返回 false | 
| getInitialClientOffset() | 返回當前拖動操作開始時指針的{x,y} client偏移量。 如果沒有拖動項目,則返回null | 
| getInitialSourceClientOffset() | 返回當前拖動操作開始時 drag source組件的根DOM節點的{x,y}client偏移量。 如果沒有拖動項目,則返回null | 
| getClientOffset() | 拖動操作正在進行時,返回指針的最后記錄的{x,y} client偏移量。 如果沒有拖動項目,則返回null | 
| getDifferenceFromInitialOffset() | 返回當前拖動操作開始時鼠標的最后記錄 client偏移量與client偏移量之間的{x,y}差異。 如果沒有拖動項目,則返回null | 
| getSourceClientOffset() | 返回 drag source組件的根DOM節點的預計{x,y}client偏移量,基於其在當前拖動操作開始時的位置以及移動差異。 如果沒有拖動項目,則返回null | 
三、DropTarget:使組件能夠放置拖拽組件
使用
DropTarget包裹住組件,使其對拖動,懸停或 dropped 的兼容項目做出反應。
使用方式
-  
          import React, { Component } from 'react';
-  
          import { DropTarget } from 'react-dnd';
-  
          
-  
          const spec = {
-  
          drop(props, monitor, component) {
-  
          // 這里 return 出去的對象屬性自行選擇,這里只是用 id 作為演示
-  
          return { id: props.id }
-  
          }
-  
          
-  
          hover(props, monitor, component) {
-  
          ...
-  
          }
-  
          
-  
          canDrop(props, monitor) {
-  
          ...
-  
          }
-  
          }
-  
          
-  
          const collect = (connect, monitor) => ({
-  
          // 這里返回一個對象,會將對象的屬性都賦到組件的 props 中去。這些屬性需要自己定義。
-  
          connectDropTarget: connect.dropTarget()
-  
          })
-  
          
-  
          
-  
          class MyComponent extends Component {
-  
          /* ... */
-  
          }
-  
          export default MyComponent;
-  
          復制代碼
參數講解:
- type: 必填。字符串,ES6符號或返回給定組件的函數props。此放置目標僅對指定類型的 drag sources項目做出反應
- spec:必填。一個普通的JavaScript對象,上面有一些允許的方法。它描述了放置目標如何對拖放事件做出反應。
- collect:必填。收集功能。它應該返回一個普通的道具對象注入你的組件。它接收兩個參數:connect 和 monitor。
- options:可選的。一個普通的對象。
spec 對象中的方法
-  drop(props, monitor, component): 可選的。在目標上放置兼容項目時調用。可以返回undefined或普通對象。如果返回一個對象,它將成為放置結果,可以使用monitor.getDropResult()獲取到。
-  hover(props, monitor, component): 可選的。當項目懸停在組件上時調用。您可以檢查monitor.isOver({ shallow: true })以測試懸停是僅發生在當前目標上還是嵌套上。
-  canDrop(props, monitor): 可選的。使用它來指定放置目標是否能夠接受該項目。如果想要始終允許它,只需省略此方法即可。
文檔沒有提供按目的處理進入或離開事件的方法。而是
monitor.isOver()從收集函數返回調用結果,以便我們可以使用componentDidUpdateReact鈎子函數來處理組件中的進入和離開事件。
方法中的參數 props, monitor, component
- props:當前組件的- props
- monitor:一個- DropTargetMonitor實例。使用它來查詢有關當前拖動狀態的信息,例如當前拖動的項目及其類型,當前和初始坐標和偏移,是否超過當前目標,以及是否可以刪除它。
- component:指定時,它是組件的實例。使用它來訪問底層DOM節點以進行位置或大小測量,或調用- setState以及其他組件方法。- canDrag方法里獲取不到- component這個參數,因為它們被調用時實例可能不可用。
collect 中的 connect 和 monitor 參數
-  connect: 一個DropTargetConnector實例。它只有一種dropTarget()方法。- dropTarget() => (elementOrNode):常用方法,返回一個函數,傳遞給組件用來將 target DOM 和 React DnD Backend 連接起來。通過{ connectDropTarget: connect.dropTarget() }從收集函數返回,可以將任何React元素標記為可放置節點。
 
-  monitor:一個 DropTargetMonitor實例。包含下面各種方法:
| 方法 | 含義 | 
|---|---|
| canDrop() | 是否可以被放置。如果正在進行拖動操作,則返回true | 
| isOver(options) | drag source是否懸停在drop target區域。可以選擇傳遞{ shallow: true }以嚴格檢查是否只有drag source懸停,而不是嵌套目標 | 
| getItemType() | 返回標識當前拖動項的類型的字符串或ES6符號。如果沒有拖動項目則返回 null | 
| getItem() | 返回表示當前拖動項的普通對象,每個拖動源都必須通過從其beginDrag()方法返回一個對象來指定它。如果沒有拖動項目則返回 null | 
| getDropResult() | 返回表示最后記錄的放置 drop result對象 | 
| didDrop() | 如果某個 drop target處理了drop事件,則返回 true,否則返回 false。即使target沒有返回drop結果,didDrop()也會返回true。 在endDrag()中使用它來測試任何放置目標是否已處理掉落。 如果在endDrag()之外調用,則返回 false | 
| getInitialClientOffset() | 返回當前拖動操作開始時指針的{x,y} client偏移量。 如果沒有拖動項目,則返回null | 
| getInitialSourceClientOffset() | 返回當前拖動操作開始時 drag source組件的根DOM節點的{x,y}client偏移量。 如果沒有拖動項目,則返回null | 
| getClientOffset() | 拖動操作正在進行時,返回指針的最后記錄的{x,y} client偏移量。 如果沒有拖動項目,則返回null | 
| getDifferenceFromInitialOffset() | 返回當前拖動操作開始時鼠標的最后記錄 client偏移量與client偏移量之間的{x,y}差異。 如果沒有拖動項目,則返回null | 
| getSourceClientOffset() | 返回 drag source組件的根DOM節點的預計{x,y}client偏移量,基於其在當前拖動操作開始時的位置以及移動差異。 如果沒有拖動項目,則返回null | 
四、DragDropContext & DragDropContextProvider
注意: 使用 DragSource 和 DropTarget 包裹的組件,必須放在: DragDropContext 包裹的根組件內部,或者 DragDropContextProvider 根標簽的內部。
DragDropContext
使用 DragDropContext 包裝應用程序的根組件以啟用 React DnD。
用法
-  
          import React, { Component } from 'react';
-  
          import HTML5Backend from 'react-dnd-html5-backend';
-  
          import { DragDropContext } from 'react-dnd';
-  
          
-  
          
-  
          class YourApp extends Component {
-  
          /* ... */
-  
          }
-  
          
-  
          export default YourApp;
-  
          復制代碼
參數
-  backend:必填。一個 React DnD 后端。除非您正在編寫自定義的,否則建議使用 React DnD 附帶的 HTML5Backend。 
-  context:backend 依賴。用於自定義后端的上下文對象。例如,HTML5Backend可以為iframe場景注入自定義窗口對象。 
DragDropContextProvider
作為 DragDropContext 的替代方法,您可以使用 DragDropContextProvider 元素為應用程序啟用React DnD。與 DragDropContext 類似,這可以通過 backendprop 注入后端,但也可以注入一個 window 對象。
用法
-  
          import React, { Component } from 'react';
-  
          import HTML5Backend from 'react-dnd-html5-backend';
-  
          import { DragDropContextProvider } from 'react-dnd';
-  
          
-  
          export default class YourApp extends Component {
-  
          render() {
-  
          return (
-  
          < DragDropContextProvider backend={HTML5Backend}>
-  
          /* ... */
-  
          </ DragDropContextProvider>
-  
          )
-  
          }
-  
          }
-  
          復制代碼
參數
-  backend:必填。一個 React DnD 后端。除非您正在編寫自定義的,否則建議使用 React DnD 附帶的 HTML5Backend。 
-  context:backend 依賴。用於自定義后端的上下文對象。例如,HTML5Backend可以為iframe場景注入自定義窗口對象。 
五、react-dnd 的簡單示例
本示例參照官方的 Dustbin 示例進行講解。
項目准備
當前項目使用 create-react-app 腳手架進行搭建,而且使用 react-dnd 時都是使用裝飾器語法進行編寫。所以需要先在項目里添加一些配置。
啟用裝飾器的配置方式可以參考我的上一篇文章:在 create-react-app 中啟用裝飾器語法。
新建 components 文件夾,用來存放編寫的組件。新建 types 文件夾,用來存放 type 字符串常量,在 types 目錄下創建 index.js 文件聲明對應的 type 值。
types/index.js
-  
          export default {
-  
          BOX: 'box'
-  
          }
-  
          復制代碼
所以當前項目 src 目錄下文件結構如下:
-  
          src
-  
          ├── components/
-  
          ├── types/
-  
          └── index.js
-  
          ├── App.js
-  
          ├── index.css
-  
          └── index.js
-  
          復制代碼
創建 Box 組件,作為 DragSource
在 components 目錄下,創建 Box.js 文件,編寫 Box 組件,使其可以進行拖動
components/Box.js
-  
          import React from 'react';
-  
          import PropTypes from 'prop-types';
-  
          import { DragSource } from 'react-dnd';
-  
          
-  
          import ItemTypes from '../types';
-  
          
-  
          const style = {
-  
          border: '1px dashed gray',
-  
          backgroundColor: 'white',
-  
          padding: '0.5rem 1rem',
-  
          marginRight: '1.5rem',
-  
          marginBottom: '1.5rem',
-  
          cursor: 'move',
-  
          float: 'left',
-  
          }
-  
          
-  
          const boxSource = {
-  
          /**
-  
          * 開始拖拽時觸發當前函數
-  
          * @param {*} props 組件的 props
-  
          */
-  
          beginDrag(props) {
-  
          // 返回的對象可以在 monitor.getItem() 中獲取到
-  
          return {
-  
          name: props.name,
-  
          }
-  
          },
-  
          
-  
          /**
-  
          * 拖拽結束時觸發當前函數
-  
          * @param {*} props 當前組件的 props
-  
          * @param {*} monitor DragSourceMonitor 對象
-  
          */
-  
          endDrag(props, monitor) {
-  
          // 當前拖拽的 item 組件
-  
          const item = monitor.getItem()
-  
          // 拖拽元素放下時,drop 結果
-  
          const dropResult = monitor.getDropResult()
-  
          
-  
          // 如果 drop 結果存在,就彈出 alert 提示
-  
          if (dropResult) {
-  
          alert( `You dropped ${item.name} into ${dropResult.name}!`)
-  
          }
-  
          },
-  
          }
-  
          
-  
          @DragSource(
-  
          // type 標識,這里是字符串 'box'
-  
          ItemTypes.BOX,
-  
          // 拖拽事件對象
-  
          boxSource,
-  
          // 收集功能函數,包含 connect 和 monitor 參數
-  
          // connect 里面的函數用來將 DOM 節點與 react-dnd 的 backend 建立聯系
-  
          (connect, monitor) => ({
-  
          // 包裹住 DOM 節點,使其可以進行拖拽操作
-  
          connectDragSource: connect.dragSource(),
-  
          // 是否處於拖拽狀態
-  
          isDragging: monitor.isDragging(),
-  
          }),
-  
          )
-  
          class Box extends React.Component {
-  
          
-  
          static propTypes = {
-  
          name: PropTypes.string.isRequired,
-  
          isDragging: PropTypes.bool.isRequired,
-  
          connectDragSource: PropTypes.func.isRequired
-  
          }
-  
          
-  
          render() {
-  
          const { isDragging, connectDragSource } = this.props
-  
          const { name } = this.props
-  
          const opacity = isDragging ? 0.4 : 1
-  
          
-  
          // 使用 connectDragSource 包裹住 DOM 節點,使其可以接受各種拖動 API
-  
          // connectDragSource 包裹住的 DOM 節點才可以被拖動
-  
          return connectDragSource && connectDragSource(
-  
          <div style={{ ...style, opacity }}>
-  
          {name}
-  
          </div>
-  
          );
-  
          }
-  
          }
-  
          
-  
          export default Box;
-  
          復制代碼
創建 Dustbin 組件,作為 DropTarget
在 components 目錄下,創建 Dustbin.js 文件,編寫 Dustbin 組件,使其可以接受對應的拖拽組件。
components/Dustbin.js
-  
          import React from 'react';
-  
          import PropTypes from 'prop-types';
-  
          
-  
          import { DropTarget } from 'react-dnd';
-  
          import ItemTypes from '../types';
-  
          
-  
          const style = {
-  
          height: '12rem',
-  
          width: '12rem',
-  
          marginRight: '1.5rem',
-  
          marginBottom: '1.5rem',
-  
          color: 'white',
-  
          padding: '1rem',
-  
          textAlign: 'center',
-  
          fontSize: '1rem',
-  
          lineHeight: 'normal',
-  
          float: 'left',
-  
          }
-  
          
-  
          const boxTarget = {
-  
          // 當有對應的 drag source 放在當前組件區域時,會返回一個對象,可以在 monitor.getDropResult() 中獲取到
-  
          drop: () => ({ name: 'Dustbin' })
-  
          }
-  
          
-  
          @DropTarget(
-  
          // type 標識,這里是字符串 'box'
-  
          ItemTypes.BOX,
-  
          // 接收拖拽的事件對象
-  
          boxTarget,
-  
          // 收集功能函數,包含 connect 和 monitor 參數
-  
          // connect 里面的函數用來將 DOM 節點與 react-dnd 的 backend 建立聯系
-  
          (connect, monitor) => ({
-  
          // 包裹住 DOM 節點,使其可以接收對應的拖拽組件
-  
          connectDropTarget: connect.dropTarget(),
-  
          // drag source是否在 drop target 區域
-  
          isOver: monitor.isOver(),
-  
          // 是否可以被放置
-  
          canDrop: monitor.canDrop(),
-  
          })
-  
          )
-  
          class Dustbin extends React.Component {
-  
          
-  
          static propTypes = {
-  
          canDrop: PropTypes.bool.isRequired,
-  
          isOver: PropTypes.bool.isRequired,
-  
          connectDropTarget: PropTypes.func.isRequired
-  
          }
-  
          
-  
          render() {
-  
          const { canDrop, isOver, connectDropTarget } = this.props;
-  
          const isActive = canDrop && isOver;
-  
          
-  
          let backgroundColor = '#222';
-  
          // 拖拽組件此時正處於 drag target 區域時,當前組件背景色變為 darkgreen
-  
          if (isActive) {
-  
          backgroundColor = 'darkgreen';
-  
          }
-  
          // 當前組件可以放置 drag source 時,背景色變為 pink
-  
          else if (canDrop) {
-  
          backgroundColor = 'darkkhaki';
-  
          }
-  
          
-  
          // 使用 connectDropTarget 包裹住 DOM 節點,使其可以接收對應的 drag source 組件
-  
          // connectDropTarget 包裹住的 DOM 節點才能接收 drag source 組件
-  
          return connectDropTarget && connectDropTarget(
-  
          <div style={{ ...style, backgroundColor }}>
-  
          {isActive ? 'Release to drop' : 'Drag a box here'}
-  
          </div>
-  
          );
-  
          }
-  
          }
-  
          
-  
          export default Dustbin;
-  
          復制代碼
在 App.js 文件中使用 DragDropContext
App.js
-  
          import React, { Component } from 'react';
-  
          import { DragDropContext } from 'react-dnd';
-  
          import HTMLBackend from 'react-dnd-html5-backend';
-  
          
-  
          import Dustbin from './components/Dustbin';
-  
          import Box from './components/Box';
-  
          
-  
          // 將 HTMLBackend 作為參數傳給 DragDropContext
-  
          @DragDropContext(HTMLBackend)
-  
          class App extends Component {
-  
          render() {
-  
          return (
-  
          <div style={{ paddingLeft: 200, paddingTop: 50 }}>
-  
          <div style={{ overflow: 'hidden', clear: 'both' }}>
-  
          <Box name="Glass" />
-  
          <Box name="Banana" />
-  
          <Box name="Paper" />
-  
          </div>
-  
          <div style={{ overflow: 'hidden', clear: 'both' }}>
-  
          <Dustbin />
-  
          </div>
-  
          </div>
-  
          );
-  
          }
-  
          }
-  
          
-  
          export default App;
-  
          復制代碼
運行項目,查看效果
運行項目:
-  
          $ npm run start
-  
          復制代碼
瀏覽器會自動打開 http://localhost:3000/ 窗口,此時可以操作瀏覽器上的 Box 組件,結合項目代碼,查看效果。 預覽效果如下:

六、本文 Demo 地址
歡迎 Star!謝謝!
七、參考鏈接
react-dnd 官方文檔 拖拽組件:React DnD 的使用
作者:暖生
鏈接:https://juejin.im/post/5c92e7fc6fb9a070e5529322
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
