React-dnd: 将monitor.getClientOffset连接到DropTarget不会刷新道具

创建于 2015-06-03  ·  22评论  ·  资料来源: react-dnd/react-dnd

我创建了一个DropTarget,并按如下所示连接了getClientOffset:

targetCollect = function (connect, monitor) {
  return {
    connectDropTarget: connect.dropTarget(),
    isOver: monitor.isOver(),
    clientOffset: monitor.getClientOffset()
  }
}

但是,在组件内移动拖动时,目标不会随着鼠标的每一次摆动而收到更新的道具(仅在进入和退出时)。 但是,目标规范的悬停_is_反复调用。 是否存在每次更改后都不注入clientOffset道具的原因?

bug wontfix

最有用的评论

+1。 遇到这个问题并浪费大量时间试图理解为什么getClientOffset()在canDrop()中可以正常工作-确实会被反复调用...-但是无法将控件转发到我所在项目的“位置”。 我需要能够在treenode的上方和下方插入,而不仅仅是“插入”它。 canDrop()无法找到让我实际告诉组件节点具有“滴”的许多“类型”中的哪一个类型的方法,以便控件可以相应地重新显示。 在canDrop中更改prop会导致错误。 订阅onMouseMove在拖放操作期间不起作用。 到目前为止,这一直是浪费时间……任何帮助将不胜感激。

@ibash ,您介意发布解决问题的要点吗?

所有22条评论

哦,好点。 没错,我没想到这种用法。 当前,唯一选择跟踪客户端偏移量的方法是使用DragLayer 。 否则,出于性能原因,仅当与_drop目标本身有关的某些内容发生更改时,才更新道具。

我会说这是一个API问题,可能有几种解决方案:

  • 禁止从collect函数内部进入getClientOffset()和类似方法,并告诉使用DragLayer代替(简单但愚蠢);
  • 在这种情况下,会自动找出用户要跟踪偏移量并订阅偏移量更改的内容(更难但更一致)。

我想我会接受PR做第二种选择。 您可以检查DragLayer实现,以了解它如何订阅偏移量更改。

我研究了第二个选项,但是它很棘手,因为collect是一个函数,因此不可能/不容易发现可能调用了哪些监视函数。

我的用例是拖动到适当位置的小部件,可能以相对复杂的方式替换其他小部件(例如在Android上拖动程序图标)。 实际上,必须根据确切的悬停位置重新排列目标,而拖动本身作为HTML5快照完全可以。 我可以通过目标规范的悬停来更新目标组件的状态。 因此,我暂时将其保留并解决它。 谢谢你的图书馆! 在几个小时的工作中,我的工作就不错。

让我们暂时保持打开状态。 与#172相同:我没想到人们会在canDrop或收集函数中使用增量。 我不会立即解决此问题,但可能会在一个月左右的时间里释放出来。

我也遇到了这个问题。 您对让客户将标志传递到DropTarget规范中以订阅偏移更改有何想法? 对于用户来说,这是另外一个“陷阱”,但是比手动订阅/取消订阅偏移量要简单。

您是否可以不使用RxJS-DOM来观察元素上的拖动事件并使用鼠标坐标来更新状态? 哈克解决方案。

  componentDidMount() {
    this._events.dragOver = DOM.fromEvent(findDOMNode(this.refs.grid), 'dragover')
                            .throttle(200)
                            .subscribe(this.getMouseCoords)
  }

我最终抓住了显示器,订阅了像DragLayer这样的偏移更改。 我还最终进入了一些地方的内部注册表。 我不认为我想做的也很寻常,因此IMO可以通过简单的api更改来解决。

有什么事吗我想在DropTarget而不是自定义拖动层中监视客户端偏移,因为当我可以从DropTarget而不是自定义拖动层访问悬停位置时,我觉得更改DropTarget的结构更容易。

+1我想做@jchristman想要做的事情。

+1。 遇到这个问题并浪费大量时间试图理解为什么getClientOffset()在canDrop()中可以正常工作-确实会被反复调用...-但是无法将控件转发到我所在项目的“位置”。 我需要能够在treenode的上方和下方插入,而不仅仅是“插入”它。 canDrop()无法找到让我实际告诉组件节点具有“滴”的许多“类型”中的哪一个类型的方法,以便控件可以相应地重新显示。 在canDrop中更改prop会导致错误。 订阅onMouseMove在拖放操作期间不起作用。 到目前为止,这一直是浪费时间……任何帮助将不胜感激。

@ibash ,您介意发布解决问题的要点吗?

最终切换到使用悬停功能对我有用。

`悬停:(道具:MyProps,监视器:DropTargetMonitor,组件:ReportItemRow)=> {
if(component!= null){
const clientOffset = monitor.getClientOffset();

        // Is the dragging node dragged above/below us as opposed to "on" us?

        const elementRect = document.elementFromPoint(clientOffset.x, clientOffset.y).getBoundingClientRect();

        const percentageY = (clientOffset.y - elementRect.top) / elementRect.height;

        // Divide the box up into .25 .5 .25
        const insertDragAndDropMagnetPercent = .25;

        if (insertDragAndDropMagnetPercent >= percentageY) {
            component.setState({ dropTarget: "above" });
        }
        else if (1 - insertDragAndDropMagnetPercent >= percentageY) {
            component.setState({ dropTarget: "inside" });
        }
        else {
            component.setState({ dropTarget: "below" });
        }
    }
},`

@ noah79 ,与此同时,您可以通过创建多个放置目标并将其放置在treenodes下来解决问题。 像这样的东西:

         _____
         _____| <-- drop div1 (above treenode1/below top of list)
treenode1_____| <-- drop div2 (on treenode1)
         _____| <-- drop div3 (below treenode1/above treenode2)
treenode2_____| <-- drop div4 (on treenode 2)
         _____| <-- drop div5 (below treenode2/above bottom of list)

最后得到2n + 1个放置目标,其中n是列表中元素的数量。 然后,基于将鼠标悬停在哪个div上,您可以更改树形列表的外观。 我已经执行了与此非常相似的操作来解决暂时无法访问getClientOffset()的问题,并且我没有注意到性能受到影响。 每个drop div的高度应为treenode1线的高度的1/2,而第一个drop div的高度应比treenode1线的高度高出第一行的1/4。 也就是说,您的CSS应该如下所示:

.dropdiv-container {
    position: absolute;
    top: -0.25em; /* If the top of this container is aligned with the top of the treenode list initially */
}
.dropdiv {
    height: 0.5em; /* If the height of treenode1 line is 1em with no padding */
}
<div className='dropdiv-container'>
    { renderDropTargets(2 * listLength + 1) }
</div>

那有意义吗?

这是我如何解决此问题的要点。 诀窍是只触及
react-dnd内部,并直接使用全局监视器。 看看
DragDropMonitor.js源代码,以了解可用的方法。
参考: https :

您必须原谅咖啡脚本:)

  # in the component being dragged, get access to the dragDropManager by adding
  # it to the contextTypes
  #
  # dragDropManager is an instance of this:
  # https://github.com/gaearon/dnd-core/blob/master/src/DragDropManager.js

  <strong i="11">@contextTypes</strong>: {
    dragDropManager: React.PropTypes.object
  }

  # because we can receive events after the component is unmounted, we need to
  # keep track of whether the component is mounted manually.
  #
  # <strong i="12">@_monitor</strong> is what lets us access all the nice internal state - it is an instance of this:
  # https://github.com/gaearon/dnd-core/blob/master/src/DragDropMonitor.js

  componentWillMount: () =>
    <strong i="13">@_isMounted</strong> = true
    <strong i="14">@_monitor</strong> = @context.dragDropManager.getMonitor()

    <strong i="15">@_unsubscribeToStateChange</strong> = @_monitor.subscribeToStateChange(@_onStateChange)
    <strong i="16">@_unsubscribeToOffsetChange</strong> = @_monitor.subscribeToOffsetChange(@_onOffsetChange)

  componentWillUnmount: () =>
    <strong i="17">@_isMounted</strong> = false
    <strong i="18">@_monitor</strong> = null

    @_unsubscribeToStateChange()
    @_unsubscribeToOffsetChange()

  # we can access dragging / dropping state by accessing the monitor 

  _onStateChange: () =>
    return unless <strong i="19">@_isMounted</strong>

    # When we stop dragging reset the counter state and hide all cursors.
    if <strong i="20">@_previousIsDragging</strong> && !@_monitor.isDragging()
      console.log('no longer dragging')
    <strong i="21">@_previousIsDragging</strong> = @_monitor.isDragging()

  _onOffsetChange: () =>
    return unless <strong i="22">@_isMounted</strong>

    # mouse is the x/y coordinates
    mouse = @_monitor.getClientOffset()

    # item is the drag item
    item = @_monitor.getItem()

    # if you want to check if a dragged item is over a target, you need the
    # targetId -- in the DropTarget wrapper you can pass it in like:
    #
    #   (connect, monitor) ->
    #     {
    #       targetId: monitor.targetId,
    #       connectDropTarget: connect.dropTarget()
    #     }
    #
    # and then you can use it like below

    @_monitor.isOverTarget(@props.targetId))

这有意义吗? 如果没有,我可以添加更多详细信息

我今天也遇到了这个问题,浪费了几个小时。 在DropTarget-> The Collecting Function下的文档中留下关于此的注释该怎么办? 至少这将使其他人免于沮丧。

顺便说一句。 关于此问题的另一种非常丑陋的破解方法可以是从dropTarget.hover()发送坐标_as state_:

const dropTarget = {
    hover(props, monitor, component) {
        // HACK! Since there is an open bug in react-dnd, making it impossible
        // to get the current client offset through the collect function as the
        // user moves the mouse, we do this awful hack and set the state (!!)
        // on the component from here outside the component.
        component.setState({
            currentDragOffset: monitor.getClientOffset(),
        });
    },
    drop() { /* ... */ },
};

当然,有很多更正确的方法可以避免这样的setState滥用。 但是至少这个小技巧非常精简,并且可以解决这个问题,直到最终解决此问题为止。 它使您可以解决该错误,而无需更改其他组件和/或文件,也无需依赖库内部。

编辑:这基本上与noah79相同,我只是之前没有读过他的代码。

真的没有办法让鼠标放下吗? 我的目标组件不需要了解它,因此setState不是我的选择。 我只需要坐标即可触发redux动作。

请忽略我的最后一条消息。 我现在看到dropendDrag是两个不同的功能,并且使光标定位在drop可以按预期工作。 :)

根据我的测试,将项目拖到目标上方时,react-dnd会消耗鼠标移动事件。 如果html后端可能不使用此事件,则当isOver属性设置为true时,目标组件可以有条件地将一个普通的mousemove侦听器放置在其dom上。 此后,当在其上拖动某些内容时,此侦听器可以按正常方式拾取鼠标位置。 我使用drag()方法中的setState使其暂时起作用,react是在渲染过渡的中间发出有关设置状态的警告。

这也把我绊倒了。 +1以获得API解决方案,或者至少获得FAQ上的内容。

有什么计划支持这一点吗?
我有一个无黑客的解决方法的想法:为DropTarget制作一个包装器,该包装器还包装hover来触发对收集函数的另一个调用,如果收集函数返回新值,则重新渲染。

为此问题+1。 我的用例是,我有几个彼此接近的放置目标。 悬停时,适当的放下目标会突出显示,但实际上放下会下降到较低的目标,这取决于您悬停的方向,这似乎很奇怪。

我偶然发现了这个问题,研究为什么偏移量没有更新,似乎还需要一种解决方法。
我接受了@ jedwards1211的想法,这就是我的想法。 HOC具有与香草DropTarget相同的接口,但在其选项中接受collectRapidly ,这是通过收集函数查询的道具列表,并在每个悬停事件上传递。 从收集函数中不加选择地传递所有道具会引起一些怪异,并且我们根本无法将connect传递给收集器,因此要防止“快速”查询connectDropTarget

因此,您可以使用RapidFireDropTarget(types, target, collect, {collectRapidly: ['offset']})而不是DropTarget(types, target, collect) ,其中offset大概是从monitor.getOffset函数系列接收其值的东西。

import React from 'react';
import {DropTarget} from 'react-dnd';
import pick from 'lodash/pick';

function RapidFireDropTarget(types, spec, collect, options={}) {
  let collectProps = {};
  const prevHover = spec.hover;
  const {collectRapidly = []} = options;

  const dummyConnect = {
    dropTarget: () => () => {
      throw new Error('Rapidly collected props cannot include results from `connect`');
    }
  };

  const WrappedComponent = Component => {
    return class extends React.Component {
      render() {
        return <Component {...this.props} {...collectProps} />;
      }
    };
  };

  spec.hover = (props, monitor, component) => {
    const allCollectProps = collect(dummyConnect, monitor);

    collectProps = pick(allCollectProps, collectRapidly);
    component.forceUpdate();

    if (prevHover instanceof Function) {
      prevHover(props, monitor, component);
    }
  }

  return (Component) => {
    return DropTarget(types, spec, collect, options)(WrappedComponent(Component));
  };
}

export default RapidFireDropTarget;

由于此问题最近没有活动,因此已被自动标记为陈旧。 如果没有进一步的活动,它将关闭。 感谢您的贡献。

此页面是否有帮助?
0 / 5 - 0 等级