Wie ziele ich auf DOM-Elemente innerhalb einer Reaktionskomponente ab oder sollte ich vermeiden, alle DOM-Elemente zusammen auszurichten?

8

Ich habe ein Skript erstellt, das beim Mouseover in einem übergeordneten Container aktiviert wird und dessen untergeordnete Elemente von der Maus entfernen soll. Ich habe es derzeit zum Laufen gebracht, aber es gibt einige Teile des Codes, die zu widersprechen scheinen, wie REACT-Code aussehen sollte. Besonders zwei Teile.

  1. Ich verwende einen Zähler in der Renderfunktion, damit jeder Bereich seine korrekte benutzerdefinierte Eigenschaft erhält. Dabei handelt es sich state.customPropertiesum ein Array, das die benutzerdefinierten Eigenschaften beim Mouseover des übergeordneten Elements aktualisiert.

    render() {
        let counter = 0;
        const returnCustomProp = function(that) {
            counter++;
            let x = 0;
            if (that.state.customProperties[counter - 1]) {
                x = that.state.customProperties[counter - 1].x;
            }
            let y = 0;
            if (that.state.customProperties[counter - 1]) {
                y = that.state.customProperties[counter - 1].y;
            }
            return "customProperty(" + x + " " + y + ")";
        }
        return (
            <div onMouseMove={this._testFunction} id="ParentContainer">
                    <section custom={returnCustomProp(this)}>
                        <b>Custom content for part 1</b>
                        <i>Could be way different from all the other elements</i>
                    </section>
                    <section custom={returnCustomProp(this)}>
                        2
                    </section>
                    <section custom={returnCustomProp(this)}>
                        <div>
                            All content can differ internally so I'm unable to create a generic element and loop trough that
                        </div>
                    </section>
                    <section custom={returnCustomProp(this)}>
                        4
                    </section>
                    <section custom={returnCustomProp(this)}>
                        5
                    </section>
                    <section custom={returnCustomProp(this)}>
                        <div>
                            <b>
                                This is just test data, the actualy data has no divs nested inside secions
                            </b>
                            <h1>
                                6
                            </h1>
                        </div>
                    </section>
                    <section custom={returnCustomProp(this)}>
                        7
                    </section>
                    <section custom={returnCustomProp(this)}>
                        8
                    </section>
                </div>
        );
    }
  2. In der Mausbewegungsfunktion verwende ich document.getElementByIdund querySelectorAllum alle Abschnittselemente abzurufen und die Mauskoordinaten von der Maus mit den Abschnittselementkoordinaten zu vergleichen.

    var mouseX = e.pageX;
    var mouseY = e.pageY;
    var spans = document.getElementById('ParentContainer').querySelectorAll('section');
    var theRangeSquared = 10 * 10;
    var maxOffset = 5;
    var newCustomProperties = [];
    for (var i = 0; i < spans.length; i++) {
        var position = spans[i].getBoundingClientRect();
        var widthMod = position.width / 2;
        var heightMod = position.height / 2;
        var coordX = position.x + widthMod;
        var coordY = position.y + heightMod + window.scrollY;
        // Distance from mouse
        var dx = coordX - mouseX;
        var dy = coordY - mouseY;
        var distanceSquared = (dx * dx + dy * dy);
        var tx = 0,
            ty = 0;
        if (distanceSquared < theRangeSquared && distanceSquared !== 0) {
            // Calculate shift scale (inverse of distance)
            var shift = maxOffset * (theRangeSquared - distanceSquared) / theRangeSquared;
            var distance = Math.sqrt(distanceSquared);
            tx = shift * dx / distance;
            ty = shift * dy / distance;
        }
        newCustomProperties.push({
            x: tx,
            y: ty
        });
    }

Ich habe das Gefühl, dass ich das alles falsch mache. Ich bin nicht sicher, wie ich den Zähler vermeiden könnte, während ich eine generische returnCustomPropFunktion behalte , um die Eigenschaften für dieses Element zurückzugeben (im Live-Code habe ich ungefähr 200 dieser Elemente, so dass das manuelle Festlegen der Array-Elementnummer für sie nicht effizient ist). .

Der zweite Teil fühlt sich hackig an, ein Element anhand seiner ID anzuvisieren, die sich innerhalb der eigentlichen Komponente befindet. Ich habe das Gefühl, ich sollte in der Lage sein, dies zu erreichen, ohne das DOM zu durchqueren. Das Verweisen auf die Abschnittselemente könnte eine Lösung sein, aber ich glaube, Verweise sollten auf ein Minimum beschränkt werden, und wie bereits erwähnt, besteht der eigentliche Code aus Hunderten dieser Abschnitte.

JSFIDDLE

Der Code macht nicht viel mehr atm als die custom="customProperty(0 0)"Eigenschaft zu aktualisieren . Sie können dies durch den Elementinspektor beim Mouseover sehen.

Kann ich diese Funktion zum <section>Funktionieren bringen, ohne die Elemente innerhalb der Renderfunktion zählen zu müssen und ohne sie verwenden zu müssen document.querySelectorAll?

timo
quelle

Antworten:

7

Wie ziele ich auf DOM-Elemente innerhalb einer Reaktionskomponente?

Gemäß den offiziellen Dokumenten von React können Sie mithilfe von Refs auf die dom-Elemente zugreifen. Sie müssen eine Referenz erstellen, indem Sie dieReact.createRef() Aufrufs .

Sollte ich es vermeiden, DOM-Elemente insgesamt anzuvisieren?

Das Durchlaufen des Dom zum Abrufen eines bestimmten Dom-Elements ist bei React keine gute Vorgehensweise, da dies zu Leistungsproblemen führen kann. Reagieren ermöglicht jedoch eine alternative Möglichkeit, dasselbe mit zu tun createRef().

Kann ich diese Funktionalität zum Laufen bringen, ohne die Elemente in der Renderfunktion zählen zu müssen und ohne document.querySelectorAll verwenden zu müssen?

Ja, beachten Sie die folgenden Schritte, um dies zu implementieren:

Erstellen Sie im Konstruktor eine Referenz für parentContainer wie folgt:

  this.ParentContainer=React.createRef();

Verwenden Sie dann parentContainer ref in render:

    <div onMouseMove={this._testFunction} id="ParentContainer" 
      ref={this.ParentContainer} >

Verwenden Sie this.parentContainer in der Testkomponente als:

//replace this 
//var spans = document.getElementById('ParentContainer').querySelectorAll('section');
//with this
  var spans = this.parentContainer.current.childNodes;

Sie können es hier überprüfen

BEARBEITEN

Irgendwelche Gedanken darüber, wie ich umgehen kann, wenn ich den Let-Zähler in der Renderfunktion verwenden muss

Sie können ein externes returnCustomPropRendering wie folgt definieren : (Hier müssen Sie den Index jedes Abschnitts anstelle einer thisReferenz übergeben.)

    returnCustomProp = (index)=> {
      let x = 0;
      if(this.state.customProperties[index]) {
          x = this.state.customProperties[index].x;
      }
      let y = 0;
      if(this.state.customProperties[index]) {
          y = this.state.customProperties[index].y;
      }
      return "customProperty("+x+" "+y+")";
    }

Und verwenden Sie es mit Abschnitt wie folgt:

   <section custom={returnCustomProp(0)}>
            <b>Custom content for part 1</b>
            <i>Could be way different from all the other elements</i>
        </section>
        <section custom={returnCustomProp(1)}>
            2
        </section>
        <section custom={returnCustomProp(2)}>
            <div>
                All content can differ internally so I'm unable to create a generic element and loop trough that
            </div>
        </section>
        <section custom={returnCustomProp(3)}>
            4
        </section>
        <section custom={returnCustomProp(4)}>
            5
        </section>
        <section custom={returnCustomProp(5)}>
            <div>
                <b>
                    This is just test data, the actual data has no divs nested inside sections
                </b>
                <h1>
                    6
                </h1>
            </div>
        </section>
        <section custom={returnCustomProp(6)}>
            7
        </section>
        <section custom={returnCustomProp(7)}>
            8
        </section>
Jatin Parmar
quelle
Ah, ich dachte, ich müsste jedem einzelnen Element einen Verweis geben. Ich wusste nicht, dass ich solche untergeordneten Elemente abfragen kann. Das ist perfekt für den DOM-Targeting-Teil. ( this.parentContainerHabe übrigens einen Fehler , sollte this.ParentContainergroß geschrieben werden). Irgendwelche Gedanken darüber, wie ich umgehen kann, wenn ich let counterdie Funktion zum Rendern innerhalb des Renderings verwenden muss?
Timo
1. sollte dies sein.ParentContainer groß geschrieben: ja 2. Irgendwelche Gedanken darüber, wie ich umgehen kann, wenn ich den Let-Zähler in der Render-Funktion verwenden muss? Ich habe versucht, es auf gegebener Geige zu entfernen
Jatin Parmar
Vielleicht schaue ich mir die falsche Version Ihrer Geige an, aber ich spreche über die Zeilen 49-61. Am besten würde ich diese außerhalb der Renderfunktion verschieben. Vielleicht liegt dies außerhalb des Rahmens der ursprünglichen Frage.
Timo
Die Lösung für das Gegenstück ist ineffizient, wenn es viele gibt sections. Ich möchte die Funktion generisch halten. Aus der Frage: (in the live code I've got about 200 of these elements so setting the array item number for them manually is not efficient).Vielleicht ist das Zählen innerhalb der Renderfunktion doch der effizienteste Weg.
Timo
0

Sie sollten DOM nicht direkt manipulieren, da der Prozess auf dem virtuellen Dom reagiert. Laut React doc sollten Sie die Ref-Weiterleitung verwenden. Für weitere Informationen lesen Sie bitte diese .

Masih Jahangiri
quelle
Könnten Sie eine Geige damit versorgen? Könnten Sie auch Informationen zu meinem angegebenen Kommentar in der Frage zur Verwendung von Referenzen angeben? : "" ... aber ich glaube, Referenzen sollten auf ein Minimum beschränkt werden und wie gesagt, der eigentliche Code besteht aus Hunderten dieser Abschnitte ... "
timo
0

Sie können die Methode findDOMNode()von ReactDOM verwenden, wenn Sie keine andere Lösung finden, als in der Dokumentation angegeben:

findDOMNodeist eine Escape-Luke, die für den Zugriff auf den zugrunde liegenden DOM-Knoten verwendet wird. In den meisten Fällen wird von der Verwendung dieser Notluke abgeraten, da sie die Komponentenabstraktion durchdringt.

Als Beispiel möchte ich einen Anwendungsfall meiner App freigeben, in dem ich den Verweis auf den Container DOM div benötige, um das Drag & Drop für die von mir verwendeten Bibliotheken zu implementieren.

Beispiel:

import React, { forwardRef } from 'react'
import _ from 'lodash'
import { List } from 'office-ui-fabric-react'
import { Draggable } from 'react-beautiful-dnd'
import ReactDOM from 'react-dom'

export const BasicItemList = forwardRef((
  {
    items,
    onClickItem,
    innerRef,
    ...rest
  }, ref) => {
  const itemToCell = (i, idx) => {
    return (
      <Draggable draggableId={id} index={idx} key={idx}>
        {
          (provided, snapshot) => {
            return (
              <MyCell item={i}
                      onClick={onClickItem}
                      innerRef={provided.innerRef}
                      isDragging={snapshot.isDragging}
                      {...provided.dragHandleProps}
                      {...provided.draggableProps}
              />
            )
          }
        }
      </Draggable>
    )
  }
  const refGetter = (comp) => {
    const div = ReactDOM.findDOMNode(comp)
    if (_.isFunction(ref)) {
      ref(comp)
    } else if (ref) {
      ref.current = comp
    }
    if (_.isFunction(innerRef)) {
      innerRef(div)
    }
  }

  return (
    <List items={items}
          onRenderCell={itemToCell}
          componentRef={refGetter}
          {...rest}
    />
  )
})
93sauu
quelle