React-dnd: Durch das Verbinden von monitor.getClientOffset mit einem DropTarget werden Requisiten nicht aktualisiert

Erstellt am 3. Juni 2015  ·  22Kommentare  ·  Quelle: react-dnd/react-dnd

Ich habe ein DropTarget erstellt und getClientOffset wie folgt verbunden:

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

Wenn Sie das Ziehen innerhalb der Komponente bewegen, erhält das Ziel jedoch nicht bei jedem Wackeln der Maus aktualisierte Requisiten (nur beim Ein- und Aussteigen). Der Schwebeflug der Zielspezifikation wird jedoch wiederholt aufgerufen. Gibt es einen Grund, warum die clientOffset-Requisite nicht bei jeder Änderung injiziert wird?

bug wontfix

Hilfreichster Kommentar

+1. Wenn Sie darauf stoßen und viel Zeit damit verschwenden, zu verstehen, warum getClientOffset () in canDrop () gut funktioniert - was wiederholt aufgerufen wird ... -, aber keine Möglichkeit, an meine Kontrolle weiterzuleiten, wo wir uns befinden. Ich muss in der Lage sein, über und unter einem Treenode einzufügen, nicht nur "darauf". canDrop () hat keine Möglichkeit, die ich finden kann, damit ich meiner Komponente tatsächlich mitteilen kann, welche der vielen "Arten" von Drops der Knoten hat, damit mein Steuerelement entsprechend erneut angezeigt werden kann. Das Ändern der Requisite in canDrop führte zu einem Fehler. Das Abonnieren von onMouseMove funktioniert beim Ziehen und Ablegen nicht. Dies war bisher eine enorme Zeitverschwendung ... jede Hilfe wäre willkommen.

@ibash , würde es Ihnen etwas

Alle 22 Kommentare

Oh, guter Punkt. Das stimmt, ich habe diese Verwendung nicht erwartet. Derzeit besteht die einzige Möglichkeit, sich für die Verfolgung des Client-Offsets zu entscheiden, in der Verwendung von DragLayer . Andernfalls werden die Requisiten aus Leistungsgründen nur aktualisiert, wenn sich etwas am _drop-Ziel selbst_ geändert hat.

Ich würde sagen, es ist ein API-Problem, und es kann verschiedene Lösungen geben:

  • Verbieten Sie, innerhalb der collect -Funktion in getClientOffset() und ähnliche Methoden zu greifen, und weisen Sie an, stattdessen DragLayer verwenden (einfach, aber dumm);
  • Stellen Sie automatisch fest, dass der Benutzer den Versatz verfolgen und die Versatzänderungen abonnieren möchte, wenn dies der Fall ist (schwieriger, aber konsistenter).

Ich denke, ich würde eine PR akzeptieren, die die zweite Option macht. Sie können die Implementierung von DragLayer überprüfen, um festzustellen, wie die Offset-Änderungen abonniert werden.

Ich habe mir die zweite Option angesehen, aber sie ist schwierig, da das Sammeln eine Funktion ist. Daher ist es nicht möglich / einfach herauszufinden, welche Monitorfunktionen aufgerufen werden könnten.

Mein Anwendungsfall ist das Herumziehen von Widgets, die an ihrer Position eingerastet sind und möglicherweise andere Widgets auf relativ komplexe Weise verschieben (denken Sie an das Ziehen von Programmsymbolen auf einem Android-Gerät). Es ist wirklich das Ziel, das sich abhängig von der genauen Schwebeposition neu anordnen muss, während das Ziehen selbst als HTML5-Snapshot vollkommen in Ordnung ist. Ich kann es mit dem Hover der Zielspezifikation tun, der den Status der Zielkomponente aktualisiert. Also lasse ich das jetzt und arbeite daran. Vielen Dank für eine tolle Bibliothek; Ich habe in wenigen Stunden etwas Anständiges zu tun.

Lassen Sie uns dies vorerst offen halten. Es ist dasselbe wie # 172: Ich habe nicht erwartet, dass Leute Deltas in canDrop oder der Sammelfunktion verwenden. Ich werde das nicht sofort beheben, aber vielleicht, nachdem ich in einem Monat oder so frei bin.

Ich bin auch auf dieses Problem gestoßen. Was halten Sie davon, dass Kunden ein Flag an die DropTarget-Spezifikation übergeben, um Offset-Änderungen zu abonnieren? Es ist ein weiteres "Gotcha" für den Benutzer, aber es ist einfacher als das manuelle Abonnieren / Abbestellen von Offsets.

Könnten Sie vielleicht nicht RxJS-DOM verwenden, um das Dragover-Ereignis auf dem Element zu beobachten und den Status mit Mauskoordinaten zu aktualisieren? Hacky Lösung vielleicht.

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

Am Ende griff ich nach dem Monitor und abonnierte Offset-Änderungen wie den DragLayer. Ich habe auch stellenweise auf die interne Registrierung zugegriffen. Ich denke nicht, dass das, was ich tun möchte, zu ungewöhnlich ist, also könnte IMO es durch einfache API-Änderungen behoben werden.

Ist etwas daraus geworden? Ich möchte den Client-Offset in meinem DropTarget und nicht in der benutzerdefinierten Drag-Ebene überwachen, da ich der Meinung bin, dass es einfacher ist, die Struktur des DropTarget zu ändern, wenn ich über das DropTarget anstelle der benutzerdefinierten Drag-Ebene auf den Hover-Speicherort zugreifen kann.

+1 Ich möchte genau das tun, was @jchristman versucht.

+1. Wenn Sie darauf stoßen und viel Zeit damit verschwenden, zu verstehen, warum getClientOffset () in canDrop () gut funktioniert - was wiederholt aufgerufen wird ... -, aber keine Möglichkeit, an meine Kontrolle weiterzuleiten, wo wir uns befinden. Ich muss in der Lage sein, über und unter einem Treenode einzufügen, nicht nur "darauf". canDrop () hat keine Möglichkeit, die ich finden kann, damit ich meiner Komponente tatsächlich mitteilen kann, welche der vielen "Arten" von Drops der Knoten hat, damit mein Steuerelement entsprechend erneut angezeigt werden kann. Das Ändern der Requisite in canDrop führte zu einem Fehler. Das Abonnieren von onMouseMove funktioniert beim Ziehen und Ablegen nicht. Dies war bisher eine enorme Zeitverschwendung ... jede Hilfe wäre willkommen.

@ibash , würde es Ihnen etwas

Der Wechsel zu Hover hat es endlich für mich getan.

`hover: (Requisiten: MyProps, Monitor: DropTargetMonitor, Komponente: ReportItemRow) => {
if (Komponente! = 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 , in der Zwischenzeit, während Sie darauf warten, dass etwas daraus wird, können Sie Ihr Problem lösen, indem Sie eine Reihe von Drop-Zielen erstellen und diese unter Ihre Treenodes legen. So etwas in der Art:

         _____
         _____| <-- 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)

Am Ende erhalten Sie 2n + 1 Drop-Ziele, wobei n die Anzahl der Elemente in Ihrer Liste ist. Anschließend können Sie basierend darauf, über welche der Divs der Mauszeiger bewegt wird, das Aussehen der Baumliste ändern. Ich habe etwas sehr Ähnliches getan, um zu umgehen, dass ich vorerst nicht auf getClientOffset () zugreifen kann, und ich habe keinen Leistungseinbruch bemerkt. Die Höhe jedes Drop-Div sollte 1/2 der Höhe der Treenode1-Linie betragen, und das erste Drop-Div sollte 1/4 der Höhe der Treenode1-Linie höher als die erste Linie liegen. Das heißt, Ihr CSS sollte folgendermaßen aussehen:

.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>

Ist das sinnvoll?

Hier ist ein Überblick darüber, wie ich das gelöst habe. Der Trick besteht darin, einfach in die zu greifen
Reagieren Sie auf Interna und verwenden Sie den globalen Monitor direkt. Schauen Sie sich das an
Ziehen Sie die Quelle DragDropMonitor.js, um eine Vorstellung davon zu erhalten, welche Methoden verfügbar sind.
Ref: https://github.com/gaearon/dnd-core/blob/master/src/DragDropMonitor.js

Du musst das Kaffeeskript entschuldigen :)

  # 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))

Macht das Sinn? Wenn nicht, kann ich weitere Details hinzufügen

Ich bin heute auch darauf gestoßen und habe einige Stunden verschwendet. Wie wäre es, wenn Sie in der Dokumentation unter DropTarget -> The Collecting Function einen Hinweis dazu hinterlassen? Das würde zumindest einige andere für Frustrationen verschonen.

Übrigens. Ein anderer wirklich hässlicher Hack um dieses Problem kann darin bestehen, die Koordinaten _as state_ von dropTarget.hover() einzusenden:

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() { /* ... */ },
};

Natürlich gibt es unzählige korrektere Möglichkeiten, um einen solchen Missbrauch von setState zu vermeiden. Aber zumindest ist dieser kleine Hack sehr komprimiert und kann die Arbeit erledigen, bis dieses Problem schließlich behoben ist. Sie können den Fehler umgehen, ohne andere Komponenten und / oder Dateien zu ändern und ohne sich auf Interna der Bibliothek verlassen zu müssen.

Bearbeiten: Dies ist im Grunde das gleiche wie noah79, ich habe seinen Code vorher nur nicht gelesen.

Gibt es wirklich keine Möglichkeit, die Mausposition auf Drop zu setzen? Meine Zielkomponente muss nichts darüber wissen, daher ist setState für mich keine Option. Ich brauche nur die Koordinaten, um eine Redux-Aktion auszulösen.

Bitte ignorieren Sie meine letzte Nachricht. Ich sehe jetzt, dass drop und endDrag zwei verschiedene Funktionen sind und die Cursorpositionierung innerhalb von drop wie erwartet funktioniert. :) :)

Aus meinen Tests geht hervor, dass react-dnd die Mausbewegungsereignisse verbraucht, sobald ein Objekt über das Ziel gezogen wird. Wenn es dem HTML-Backend möglich wäre, dieses Ereignis nicht zu nutzen, könnte die Zielkomponente einen normalen Mausbewegungs-Listener nur bedingt auf seinem Dom platzieren, wenn die Eigenschaft isOver auf true gesetzt ist. Danach könnte dieser Hörer die Mausposition (auf normale Weise) erfassen, wenn etwas darüber gezogen wird. Ich habe dies vorübergehend mit setState von der drag () -Methode funktionieren lassen. React gibt Warnungen über das Festlegen des Status während eines Renderübergangs.

Das hat mich auch gestolpert. +1 für eine API-Lösung oder zumindest etwas in den FAQ.

Gibt es einen Plan, um dies zu unterstützen?
Ich habe eine Idee für eine hackfreie Problemumgehung: Erstellen Sie einen Wrapper für DropTarget , der auch hover umschließt, um einen weiteren Aufruf der Erfassungsfunktion und ein erneutes Rendern auszulösen, wenn die Erfassungsfunktion neue Werte zurückgibt .

+1 für dieses Problem. Ich habe einen Anwendungsfall, bei dem ich mehrere Drop-Ziele habe, die sich in unmittelbarer Nähe zueinander befinden. Es scheint seltsam, dass beim Schweben das richtige Drop-Ziel hervorgehoben wird, aber tatsächlich fällt es auf ein niedrigeres Ziel, je nachdem, aus welcher Richtung Sie schwebten.

Ich bin über dieses Problem gestolpert und habe untersucht, warum Offsets nicht aktualisiert werden. Es scheint, dass noch eine Problemumgehung erforderlich ist.
Ich habe die Idee von @ jedwards1211 aufgegriffen und hier ist, was ich mir DropTarget , akzeptiert jedoch collectRapidly in seinen Optionen. Dies ist eine Liste von Requisiten, die über die Sammelfunktion abgefragt und bei jedem Hover-Ereignis weitergegeben werden können. Das wahllose Übergeben aller Requisiten von der Sammelfunktion verursachte einige Verrücktheiten und wir können connect an den Sammler weitergeben, so dass es einen Schutz davor gibt, connectDropTarget "schnell" abzufragen.

Anstelle von DropTarget(types, target, collect) würden Sie also RapidFireDropTarget(types, target, collect, {collectRapidly: ['offset']}) , wobei offset vermutlich etwas ist, das seinen Wert von der Funktionsfamilie monitor.getOffset erhält.

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;

Dieses Problem wurde automatisch als veraltet markiert, da es in letzter Zeit keine Aktivitäten gab. Es wird geschlossen, wenn keine weitere Aktivität stattfindet. Vielen Dank für Ihre Beiträge.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen