react-dnd 用法詳解


本文詳細講解了 react-dnd 的 API 以及用法,並且附上了可供參考的 Demo,希望能夠給需要的朋友提供一下幫助。


一、概念

React DnD 是一組 React 高階組件,使用的時候只需要使用對應的 API 將目標組件進行包裹,即可實現拖動或接受拖動元素的功能。將拖動的事件轉換成對象中對應狀態的形式,不需要開發者自己判斷拖動狀態,只需要在傳入的 spec 對象中各個狀態屬性中做對應處理即可。剛剛接觸可能難以理解,真正熟悉用法之后會感覺很方便。

本文 Demo 地址:react-dnd-dustbin。如有幫助,歡迎 Star。


二、DragSource:使組件能夠被拖拽

使用 DragSource 包裹住組件,使其可以進行拖動。

使用方式

  1.  
    import React, { Component } from 'react';
  2.  
    import { DragSource } from 'react-dnd';
  3.  
     
  4.  
    const spec = {
  5.  
    beginDrag(props, monitor, component) {
  6.  
    // 這里 return 出去的對象屬性自行選擇,這里只是用 id 作為演示
  7.  
    return { id: props.id }
  8.  
    }
  9.  
     
  10.  
    endDrag(props, monitor, component) {
  11.  
    ...
  12.  
    }
  13.  
     
  14.  
    canDrag(props, monitor) {
  15.  
    ...
  16.  
    }
  17.  
     
  18.  
    isDragging(props, monitor) {
  19.  
    ...
  20.  
    }
  21.  
    }
  22.  
     
  23.  
    const collect = (connect, monitor) => ({
  24.  
    // 這里返回一個對象,會將對象的屬性都賦到組件的 props 中去。這些屬性需要自己定義。
  25.  
    connectDropTarget: connect.dropTarget(),
  26.  
    id: monitor.getItem().id
  27.  
    })
  28.  
     
  29.  
    @DragSource(type, spec, collect)
  30.  
    class MyComponent extends Component {
  31.  
    /* ... */
  32.  
    }
  33.  
     
  34.  
    export default MyComponent;
  35.  
    復制代碼

參數講解:

  • 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 被調用。對於每個 beginDragendDrag 都會對應。

  • 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 連接起來
  • 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 的兼容項目做出反應。

使用方式

  1.  
    import React, { Component } from 'react';
  2.  
    import { DropTarget } from 'react-dnd';
  3.  
     
  4.  
    const spec = {
  5.  
    drop(props, monitor, component) {
  6.  
    // 這里 return 出去的對象屬性自行選擇,這里只是用 id 作為演示
  7.  
    return { id: props.id }
  8.  
    }
  9.  
     
  10.  
    hover(props, monitor, component) {
  11.  
    ...
  12.  
    }
  13.  
     
  14.  
    canDrop(props, monitor) {
  15.  
    ...
  16.  
    }
  17.  
    }
  18.  
     
  19.  
    const collect = (connect, monitor) => ({
  20.  
    // 這里返回一個對象,會將對象的屬性都賦到組件的 props 中去。這些屬性需要自己定義。
  21.  
    connectDropTarget: connect.dropTarget()
  22.  
    })
  23.  
     
  24.  
    @DropTarget(type, spec, collect)
  25.  
    class MyComponent extends Component {
  26.  
    /* ... */
  27.  
    }
  28.  
    export default MyComponent;
  29.  
    復制代碼

參數講解:

  • 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。

用法

  1.  
    import React, { Component } from 'react';
  2.  
    import HTML5Backend from 'react-dnd-html5-backend';
  3.  
    import { DragDropContext } from 'react-dnd';
  4.  
     
  5.  
    @DragDropContext(HTML5Backend)
  6.  
    class YourApp extends Component {
  7.  
    /* ... */
  8.  
    }
  9.  
     
  10.  
    export default YourApp;
  11.  
    復制代碼

參數

  • backend:必填。一個 React DnD 后端。除非您正在編寫自定義的,否則建議使用 React DnD 附帶的 HTML5Backend。

  • context:backend 依賴。用於自定義后端的上下文對象。例如,HTML5Backend可以為iframe場景注入自定義窗口對象。

DragDropContextProvider

作為 DragDropContext 的替代方法,您可以使用 DragDropContextProvider 元素為應用程序啟用React DnD。與 DragDropContext 類似,這可以通過 backendprop 注入后端,但也可以注入一個 window 對象。

用法

  1.  
    import React, { Component } from 'react';
  2.  
    import HTML5Backend from 'react-dnd-html5-backend';
  3.  
    import { DragDropContextProvider } from 'react-dnd';
  4.  
     
  5.  
    export default class YourApp extends Component {
  6.  
    render() {
  7.  
    return (
  8.  
    < DragDropContextProvider backend={HTML5Backend}>
  9.  
    /* ... */
  10.  
    </ DragDropContextProvider>
  11.  
    )
  12.  
    }
  13.  
    }
  14.  
    復制代碼

參數

  • 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

  1.  
    export default {
  2.  
    BOX: 'box'
  3.  
    }
  4.  
    復制代碼

所以當前項目 src 目錄下文件結構如下:

  1.  
    src
  2.  
    ├── components/
  3.  
    ├── types/
  4.  
    └── index.js
  5.  
    ├── App.js
  6.  
    ├── index.css
  7.  
    └── index.js
  8.  
    復制代碼

創建 Box 組件,作為 DragSource

在 components 目錄下,創建 Box.js 文件,編寫 Box 組件,使其可以進行拖動

components/Box.js

  1.  
    import React from 'react';
  2.  
    import PropTypes from 'prop-types';
  3.  
    import { DragSource } from 'react-dnd';
  4.  
     
  5.  
    import ItemTypes from '../types';
  6.  
     
  7.  
    const style = {
  8.  
    border: '1px dashed gray',
  9.  
    backgroundColor: 'white',
  10.  
    padding: '0.5rem 1rem',
  11.  
    marginRight: '1.5rem',
  12.  
    marginBottom: '1.5rem',
  13.  
    cursor: 'move',
  14.  
    float: 'left',
  15.  
    }
  16.  
     
  17.  
    const boxSource = {
  18.  
    /**
  19.  
    * 開始拖拽時觸發當前函數
  20.  
    * @param {*} props 組件的 props
  21.  
    */
  22.  
    beginDrag(props) {
  23.  
    // 返回的對象可以在 monitor.getItem() 中獲取到
  24.  
    return {
  25.  
    name: props.name,
  26.  
    }
  27.  
    },
  28.  
     
  29.  
    /**
  30.  
    * 拖拽結束時觸發當前函數
  31.  
    * @param {*} props 當前組件的 props
  32.  
    * @param {*} monitor DragSourceMonitor 對象
  33.  
    */
  34.  
    endDrag(props, monitor) {
  35.  
    // 當前拖拽的 item 組件
  36.  
    const item = monitor.getItem()
  37.  
    // 拖拽元素放下時,drop 結果
  38.  
    const dropResult = monitor.getDropResult()
  39.  
     
  40.  
    // 如果 drop 結果存在,就彈出 alert 提示
  41.  
    if (dropResult) {
  42.  
    alert( `You dropped ${item.name} into ${dropResult.name}!`)
  43.  
    }
  44.  
    },
  45.  
    }
  46.  
     
  47.  
    @DragSource(
  48.  
    // type 標識,這里是字符串 'box'
  49.  
    ItemTypes.BOX,
  50.  
    // 拖拽事件對象
  51.  
    boxSource,
  52.  
    // 收集功能函數,包含 connect 和 monitor 參數
  53.  
    // connect 里面的函數用來將 DOM 節點與 react-dnd 的 backend 建立聯系
  54.  
    (connect, monitor) => ({
  55.  
    // 包裹住 DOM 節點,使其可以進行拖拽操作
  56.  
    connectDragSource: connect.dragSource(),
  57.  
    // 是否處於拖拽狀態
  58.  
    isDragging: monitor.isDragging(),
  59.  
    }),
  60.  
    )
  61.  
    class Box extends React.Component {
  62.  
     
  63.  
    static propTypes = {
  64.  
    name: PropTypes.string.isRequired,
  65.  
    isDragging: PropTypes.bool.isRequired,
  66.  
    connectDragSource: PropTypes.func.isRequired
  67.  
    }
  68.  
     
  69.  
    render() {
  70.  
    const { isDragging, connectDragSource } = this.props
  71.  
    const { name } = this.props
  72.  
    const opacity = isDragging ? 0.4 : 1
  73.  
     
  74.  
    // 使用 connectDragSource 包裹住 DOM 節點,使其可以接受各種拖動 API
  75.  
    // connectDragSource 包裹住的 DOM 節點才可以被拖動
  76.  
    return connectDragSource && connectDragSource(
  77.  
    <div style={{ ...style, opacity }}>
  78.  
    {name}
  79.  
    </div>
  80.  
    );
  81.  
    }
  82.  
    }
  83.  
     
  84.  
    export default Box;
  85.  
    復制代碼

創建 Dustbin 組件,作為 DropTarget

在 components 目錄下,創建 Dustbin.js 文件,編寫 Dustbin 組件,使其可以接受對應的拖拽組件。

components/Dustbin.js

  1.  
    import React from 'react';
  2.  
    import PropTypes from 'prop-types';
  3.  
     
  4.  
    import { DropTarget } from 'react-dnd';
  5.  
    import ItemTypes from '../types';
  6.  
     
  7.  
    const style = {
  8.  
    height: '12rem',
  9.  
    width: '12rem',
  10.  
    marginRight: '1.5rem',
  11.  
    marginBottom: '1.5rem',
  12.  
    color: 'white',
  13.  
    padding: '1rem',
  14.  
    textAlign: 'center',
  15.  
    fontSize: '1rem',
  16.  
    lineHeight: 'normal',
  17.  
    float: 'left',
  18.  
    }
  19.  
     
  20.  
    const boxTarget = {
  21.  
    // 當有對應的 drag source 放在當前組件區域時,會返回一個對象,可以在 monitor.getDropResult() 中獲取到
  22.  
    drop: () => ({ name: 'Dustbin' })
  23.  
    }
  24.  
     
  25.  
    @DropTarget(
  26.  
    // type 標識,這里是字符串 'box'
  27.  
    ItemTypes.BOX,
  28.  
    // 接收拖拽的事件對象
  29.  
    boxTarget,
  30.  
    // 收集功能函數,包含 connect 和 monitor 參數
  31.  
    // connect 里面的函數用來將 DOM 節點與 react-dnd 的 backend 建立聯系
  32.  
    (connect, monitor) => ({
  33.  
    // 包裹住 DOM 節點,使其可以接收對應的拖拽組件
  34.  
    connectDropTarget: connect.dropTarget(),
  35.  
    // drag source是否在 drop target 區域
  36.  
    isOver: monitor.isOver(),
  37.  
    // 是否可以被放置
  38.  
    canDrop: monitor.canDrop(),
  39.  
    })
  40.  
    )
  41.  
    class Dustbin extends React.Component {
  42.  
     
  43.  
    static propTypes = {
  44.  
    canDrop: PropTypes.bool.isRequired,
  45.  
    isOver: PropTypes.bool.isRequired,
  46.  
    connectDropTarget: PropTypes.func.isRequired
  47.  
    }
  48.  
     
  49.  
    render() {
  50.  
    const { canDrop, isOver, connectDropTarget } = this.props;
  51.  
    const isActive = canDrop && isOver;
  52.  
     
  53.  
    let backgroundColor = '#222';
  54.  
    // 拖拽組件此時正處於 drag target 區域時,當前組件背景色變為 darkgreen
  55.  
    if (isActive) {
  56.  
    backgroundColor = 'darkgreen';
  57.  
    }
  58.  
    // 當前組件可以放置 drag source 時,背景色變為 pink
  59.  
    else if (canDrop) {
  60.  
    backgroundColor = 'darkkhaki';
  61.  
    }
  62.  
     
  63.  
    // 使用 connectDropTarget 包裹住 DOM 節點,使其可以接收對應的 drag source 組件
  64.  
    // connectDropTarget 包裹住的 DOM 節點才能接收 drag source 組件
  65.  
    return connectDropTarget && connectDropTarget(
  66.  
    <div style={{ ...style, backgroundColor }}>
  67.  
    {isActive ? 'Release to drop' : 'Drag a box here'}
  68.  
    </div>
  69.  
    );
  70.  
    }
  71.  
    }
  72.  
     
  73.  
    export default Dustbin;
  74.  
    復制代碼

在 App.js 文件中使用 DragDropContext

App.js

  1.  
    import React, { Component } from 'react';
  2.  
    import { DragDropContext } from 'react-dnd';
  3.  
    import HTMLBackend from 'react-dnd-html5-backend';
  4.  
     
  5.  
    import Dustbin from './components/Dustbin';
  6.  
    import Box from './components/Box';
  7.  
     
  8.  
    // 將 HTMLBackend 作為參數傳給 DragDropContext
  9.  
    @DragDropContext(HTMLBackend)
  10.  
    class App extends Component {
  11.  
    render() {
  12.  
    return (
  13.  
    <div style={{ paddingLeft: 200, paddingTop: 50 }}>
  14.  
    <div style={{ overflow: 'hidden', clear: 'both' }}>
  15.  
    <Box name="Glass" />
  16.  
    <Box name="Banana" />
  17.  
    <Box name="Paper" />
  18.  
    </div>
  19.  
    <div style={{ overflow: 'hidden', clear: 'both' }}>
  20.  
    <Dustbin />
  21.  
    </div>
  22.  
    </div>
  23.  
    );
  24.  
    }
  25.  
    }
  26.  
     
  27.  
    export default App;
  28.  
    復制代碼

運行項目,查看效果

運行項目:

  1.  
    $ npm run start
  2.  
    復制代碼

瀏覽器會自動打開 http://localhost:3000/ 窗口,此時可以操作瀏覽器上的 Box 組件,結合項目代碼,查看效果。 預覽效果如下:

預覽效果

 

 


六、本文 Demo 地址

react-dnd-dustbin

歡迎 Star!謝謝!


七、參考鏈接

react-dnd 官方文檔 拖拽組件:React DnD 的使用


作者:暖生
鏈接:https://juejin.im/post/5c92e7fc6fb9a070e5529322
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM