¿Cómo apunto a elementos DOM dentro de un componente de reacción, o debo evitar apuntar elementos DOM juntos?

8

Creé un script que se activa al pasar el mouse sobre un contenedor primario y debería alejar sus elementos secundarios del mouse. Actualmente lo tengo funcionando, pero hay algunas partes del código que parecen contradecir cómo debería ser el código REACT. Especialmente dos partes.

  1. Estoy usando un contador en la función de representación para que cada intervalo obtenga su propiedad personalizada correcta, de la state.customPropertiescual hay una matriz que actualiza las propiedades personalizadas al pasar el mouse del elemento principal.

    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. En la función mousemove que estoy usando document.getElementByIdy querySelectorAllpara obtener todos los elementos de sección y comparar las coordenadas del mouse del mouse con las coordenadas de los elementos de sección.

    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
        });
    }

Tengo la sensación de que voy a hacer todo esto mal. No estoy seguro de cómo podría evitar el contador mientras mantengo una returnCustomPropfunción genérica para devolver las propiedades de dicho elemento (en el código en vivo tengo alrededor de 200 de estos elementos, por lo que configurar el número de elemento de la matriz manualmente no es eficiente) .

La segunda parte se siente extraña al apuntar a un elemento por ID que está dentro del componente real. Siento que debería poder apuntar a esto sin atravesar el DOM. Hacer referencia a los elementos de la sección podría ser una solución, pero creo que las referencias deben mantenerse al mínimo y, como se indicó, el código real consta de cientos de estas secciones.

JSFIDDLE

El código no hace mucho más cajero automático que actualizar la custom="customProperty(0 0)"propiedad. Puede ver que esto sucede a través del inspector de elementos al pasar el mouse.

¿Puedo hacer que esta funcionalidad funcione sin tener que contar los <section>elementos dentro de la función de renderizado y sin tener que usarla document.querySelectorAll?

timo
fuente

Respuestas:

7

¿Cómo apunto los elementos DOM dentro de un componente reactivo?

Según los documentos oficiales de React, puede acceder a los elementos dom mediante el uso de referencias. Tienes que crear una referencia usando la React.createRef()llamada.

¿Debo evitar apuntar a elementos DOM por completo?

Atravesar el dom para obtener un elemento dom particular no es una buena práctica con React, ya que causará un problema de rendimiento. Sin embargo, reaccionar permite una forma alternativa de hacer lo mismo mediante el uso createRef().

¿Puedo hacer que esta funcionalidad funcione sin tener que contar los elementos dentro de la función de renderizado y sin tener que usar document.querySelectorAll?

Sí, considere los siguientes pasos para implementar esto:

Dentro del constructor, cree una referencia para parentContainer como esta:

  this.ParentContainer=React.createRef();

Luego use parentContainer ref en render:

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

Dentro del componente de prueba, use this.parentContainer como:

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

Puedes consultarlo aquí

EDITAR

Alguna idea sobre cómo puedo evitar tener que usar el contador de let dentro de la función de render

Puede definir el returnCustomProprenderizado externo de esta manera: (Aquí deberá pasar el índice de cada sección en lugar de una thisreferencia)

    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+")";
    }

Y úsalo con una sección como esta:

   <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
fuente
Ah, pensé que tendría que poner una referencia en cada elemento individual. No sabía que podía consultar a través de los elementos secundarios como ese. Eso es perfecto para la parte de orientación DOM. (Obtuve un error en this.parentContainerBTW, debe ser this.ParentContainercapitalizado). ¿Alguna idea sobre cómo puedo evitar tener que usar el let counterinterior de la función de renderizado?
timo
1.debe ser esto.ParentContainer en mayúscula: sí 2. ¿Alguna idea sobre cómo puedo evitar tener que usar el contador de let dentro de la función de renderizado? He intentado eliminarlo en violín dado
Jatin Parmar
Tal vez estoy mirando la versión incorrecta de tu violín, pero estoy hablando de las líneas 49-61. Preferiblemente los movería fuera de la función de renderizado. Quizás esto está fuera del alcance de la pregunta original.
timo
La solución para la contraparte es ineficiente para cuando hay muchos sections. Me gustaría mantener la función genérica. De la pregunta: (in the live code I've got about 200 of these elements so setting the array item number for them manually is not efficient).Quizás contar dentro de la función de renderizado es la forma más eficiente después de todo.
timo
0

No debe manipular DOM directamente porque reacciona el proceso en dom virtual. De acuerdo con el documento React, debes usar el reenvío de referencias. Para más detalles por favor lea esto .

Masih Jahangiri
fuente
¿Podría proporcionar un violín con esto? Además, ¿podría proporcionar información sobre mi comentario declarado en la pregunta sobre el uso de referencias? : "" ... pero creo que las referencias deben mantenerse al mínimo y, como se indicó, el código real consta de cientos de estas secciones ... "
timo
0

Puede usar el método findDOMNode()de ReactDOM si no encuentra otra solución pero cómo dice la documentación:

findDOMNodees una trampilla de escape utilizada para acceder al nodo DOM subyacente. En la mayoría de los casos, se desaconseja el uso de esta trampilla de escape porque perfora la abstracción del componente.

Como ejemplo, quiero compartir un caso de uso de mi aplicación donde necesito la referencia al contenedor DOM div para implementar el arrastrar y soltar para las bibliotecas que estaba usando.

Ejemplo:

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
fuente