react-dnd

435 阅读6分钟

教程

juejin.cn/post/715504… react-dnd.github.io/react-dnd/d…

使用

DndProvider 的本质是一个由 React.createContext 创建一个上下文的容器(组件),用于控制拖拽的行为,数据的共享,类似于 react-redux 的 Provider。

  • react-dnd-html5-backend : 用于控制 html5 事件的 backend
import { HTML5Backend } from "react-dnd-html5-backend";
import { DndProvider } from "react-dnd";

<DndProvider backend={HTML5Backend}>
  <TutorialApp />
</DndProvider>;

useDrag

使用 useDrag 可创建一个拖动源,他会 ref 用于绑定拖动源

参数

  1. 第一个参数,是一个函数返回一个对象,是用于描述了 drag 的配置信息和常用属性
  • type:必填。这必须是字符串或 symbol。只有为相同类型注册的放置目标才会对此项目做出反应
  • item:必填。对象或函数,拖动源的信息。
    • 当是对象时,这是放置目标可获得的有关拖动源的唯一信息
    • 当这是一个函数时,它会在拖动操作开始时触发,并返回一个表示拖动操作的对象(请参见第一个项目符号)。如果返回 null,则拖动操作被取消。
  • end(item, monitor):选填。当拖动停止时,end 被调用,monitor 是当前拖动源实例
    • monitor.didDrop()可以判断当前拖动源是否掉落在了放置目标里,放在了空白的地方就会返回 false
  • canDrag(monitor):选填。返回一个布尔值,表示是否可以拖拽
  • isDragging(monitor):选填。返回一个布尔值,表示是否在拖拽中。可以覆盖 Monitor 对象中的 isDragging 方法,从而决定 collect 里返回的 isDragging
  • collect(monitor):选填.他返回一个对象,就是 useDrag,返回结果解构的第一个值
  1. 用于记忆的依赖数组。这就像内置的 useMemoReact hook 一样。默认值为空数组,所以如果有依赖一定要加到数组里

返回值

返回一个数组可以解构 3 个参数

  1. 从 collect 函数收集的属性的对象
  2. 当前拖动的 ref
  3. 拖动之后留在原地的 ref

解释

  1. item:拖动源的 item 参数,这个 item 只是临时的可以修改他,不会影响原来的数据,重新拖动之后就会变了

  2. monitor 实例:他是一个对象有以下方法

    如果被drop组件包裹了,计算鼠标偏移量的好像都获取不到了,可以在drop中return出来,然后在getDropResult方法里获取

    • canDrag():返回一个布尔值。表示是否可以拖动,如果第一个参数里的 canDrag() 返回 true 或未定义,则返回 true。
    • isDragging():返回一个布尔值。表示是否正在拖动,或者 isDragging() 已定义并返回 true,则返回 true。
    • getItemType():返回字符串。获取拖动源的 type 属性,就是第一个参数里的 type
    • getItem():返回一个对象。获取拖动源的 item 属性,就是第一个参数里的 item
    • didDrop():返回一个布尔值。判断当前拖动源是否掉落在了放置目标里,放在了空白的地方就会返回 false
    • getDropResult():可以获取 useDrop 的 drop 方法里 return 的对象

    下面的好像都是计算鼠标偏移量的

    • getInitialClientOffset():返回{ x, y }当前拖动操作开始时鼠标的距离左边和顶部的距离
    • getInitialSourceClientOffset():返回{ x, y }操作开始时拖动源组件的左上角距离左边和顶部的距离
    • getClientOffset():返回{ x, y }返回鼠标最后的位置
    • getDifferenceFromInitialOffset():返回{ x, y }最后记录的鼠标位置与拖动操作开始时的鼠标值之间的差值
    • getSourceClientOffset(): x, y }返回拖动源组件的左上角距离左边和顶部的距离

useDrop

参数

  1. 第一个参数,是一个函数返回一个对象,是用于描述了 drop 的配置信息和常用属性
    • accept:必填。这必须是字符串或 symbol。只有和 useDrag 的 type 相同才行,要不然什么都不会触发
    • drop(item, monitor):选填:当兼容项目掉落到目标上时调用。如果返回一个对象,可以在拖动源的 end 方法里用 monitor.getDropResult()获取到
    • hover(item, monitor):当项目悬停在组件上时调用
    • canDrop(item, monitor):返回一个布尔值,表示拖拽物是否可以放置
    • collect(monitor):选填.他返回一个对象,就是 useDrop,返回结果解构的第一个值
  2. 用于记忆的依赖数组。这就像内置的 useMemoReact hook 一样。默认值为空数组,所以如果有依赖一定要加到数组里

返回值

返回一个数组可以解构 2 个参数

  1. 从 collect 函数收集的属性的对象
  2. 当前盒子的 ref

解释

  1. item:拖动源的 item 参数,这个 item 只是临时的可以修改他,不会影响原来的数据,重新拖动之后就会变了

  2. monitor 实例:他是一个对象有以下方法

    • canDrop():返回一个布尔值。表示是否可以掉落,如果第一个参数里的 canDrop() 返回 true 或未定义,则返回 true。
    • isOver({shallow?: boolean;}):返回一个布尔值。表示指针当前是否悬停在所有者上方。传递 {shallow: true} 来严格检查是否只有所有者被悬停,而不是 到嵌套目标
    • getItem():获取拖拽源的 item
    • getDropResult():获取第一个参数里 drop 方法返回的对象
    • didDrop():返回一个布尔值

    下面的好像都是计算鼠标偏移量的

    • getInitialClientOffset():返回{ x, y }当前拖动操作开始时鼠标的距离左边和顶部的距离
    • getInitialSourceClientOffset():返回{ x, y }操作开始时拖动源组件的左上角距离左边和顶部的距离
    • getClientOffset():返回{ x, y }返回鼠标最后的位置
    • getDifferenceFromInitialOffset():返回{ x, y }最后记录的鼠标位置与拖动操作开始时的鼠标值之间的差值
    • getSourceClientOffset(): x, y }返回拖动源组件的左上角距离左边和顶部的距离
//1.引用useDrag
import { useDrag } from 'react-dnd'

function DraggableComponent(props) {
  const [collected, drag, dragPreview] = useDrag(() => ({
    type:'',
    item: { id },
    collect: (monitor: any) => ({
    //当传入isDragging方法时,monitor.isDragging()方法指代传入的方法
  isDragging: monitor.isDragging(),
}),
  }))
  return collected.isDragging ? (
    <div ref={dragPreview} />
  ) : (
    <div ref={drag} {...collected}>
      ...
    </div>
  )
}


function MyDropTarget(props) {
  const {data,update} = useContext<any>(myConText);
  const [collectedProps, drop] = useDrop(() => ({
    drop:(item:any,monitor)=>{
      update({ids:[...data?.ids,item?.id]})
    },
    collect: (monitor) => {
      return {
      isDragging: monitor.isDragging(),
      getDropResult:monitor.getDropResult(),
      getDifferenceFromInitialOffset:monitor.getDifferenceFromInitialOffset()
    }}
    accept:'BOX'
  }),[data])

  return <div ref={drop} className='drops'>
    {data?.ids?.map((item,index)=>{
    return <Box key={index} id={item}/>
    })}
  </div>
}

自由拖动案例

export default function Text() {
  const [dropList, setDropList] = useState([1, 2, 3, 4, 5]);

  return (
    <DndProvider backend={HTML5Backend}>
      <MyDrop>
        <>
          <Word type="box" text={"myDrag"} id={1} />
        </>
      </MyDrop>

    </DndProvider>
  );
}
const MyDrop = (props) => {
  const [, drop] = useDrop(() => ({
    accept: "box",
    drop(_item: any, monitor: any) {
      const delta = monitor.getDifferenceFromInitialOffset();
      const left = Math.round(delta.x);
      const top = Math.round(delta.y);
      return { top, left };
    },
  }));

  return (
    <div ref={drop} style={{ width: "100%", height: "100%" }}>
      {props.children}
    </div>
  );
};

const Word = ({ type, text, id, ...props }: any) => {
  const [offsetX, setOffsetX] = useState(0);
  const [offsetY, setOffsetY] = useState(0);
  const [{ isDragging }, drag]: any = useDrag(() => ({
    type,
    item: { id, type },
    end(item, monitor) {
      let top = 0,
        left = 0;
      if (monitor.didDrop()) {
        const dropRes = monitor.getDropResult() as any;
        if (dropRes) {
          top = dropRes.top;
          left = dropRes.left;
        }
        setOffsetX((offsetX) => offsetX + left);
        setOffsetY((offsetY) => offsetY + top);
      } else {
        setOffsetX(0);
        setOffsetY(0);
      }
    },
    collect: (monitor) => ({
      isDragging: !!monitor.isDragging(),
    }),
  }));

  return (
    <div
      className="word_drag"
      id={id}
      ref={drag}
      style={{
        opacity: isDragging ? 0 : 1,
        top: `${offsetY}px`,
        left: `${offsetX}px`,
      }}
    >
      {text}
    </div>
  );
};