我该如何在React组件中定位DOM元素,或者说我应该避免直接操作DOM元素?

9
我创建了一个脚本,在父容器上激活鼠标悬停,应该将其子元素移开鼠标。目前它可以工作,但是代码中有一些部分似乎与REACT代码应该看起来不同。特别是两个部分。
  1. I'm using a counter in the render function so that each span gets it's correct custom property from state.customProperties which is an array that updates the custom properties on mouseover of the parent element.

    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 the mousemove function I'm using document.getElementById and querySelectorAll to get all the section elements and compare the mouse coordinates from the mouse to the section elements coordinates.

    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
        });
    }
    
我觉得我的做法不对。我不确定如何在保持通用的returnCustomProp函数返回所述元素的属性的同时避免计数器(在实际代码中,我大约有200个这些元素,因此手动设置数组项编号是低效的)。
第二部分感觉像是通过ID来定位实际组件内部的元素,这种方法有点hacky。我认为我应该能够在不遍历DOM的情况下定位到它。引用部分元素可能是一个解决方案,但我认为应该尽量减少引用,正如所述,实际代码由数百个这些部分组成。 JSFIDDLE 目前,该代码并没有做更多的事情,只是更新了custom="customProperty(0 0)"属性。您可以通过鼠标悬停在元素检查器上看到这一点。 我能否在不使用document.querySelectorAll的情况下使此功能正常工作,而无需计算渲染函数中的<section>元素数量?
3个回答

9

如何在React组件中定位DOM元素?

根据React官方文档,可以使用ref访问dom元素。您需要使用React.createRef()方法创建一个ref。

我应该完全避免定位DOM元素吗?

遍历DOM以获取特定的DOM元素不是React中良好的实践,因为它会导致性能问题。但是,React允许使用createRef()来实现相同的目的。

我能否在不使用render函数内部计算元素数量和document.querySelectorAll的情况下使此功能正常工作?

可以,考虑以下步骤来实现:

在构造函数中,像这样为parentContainer创建一个ref:

  this.ParentContainer=React.createRef();

然后在render函数中使用parentContainer引用:

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

在测试组件内,使用this.parentContainer作为:

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

您可以在此处检查:这里 编辑
有什么想法,我怎样可以解决不得不在render函数内使用let计数器的问题?
您可以像这样在render之外定义returnCustomProp: (在这里,您需要传递每个部分的索引,而不是 this 引用)
    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+")";
    }

您可以像这样将其与<section>标签一起使用:

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

啊,我想我必须在每个单独的元素上放置一个引用。不知道我可以像那样通过子元素查询。这对于DOM定位部分非常完美。(顺便说一句,this.parentContainer出现了错误,应该是大写的this.ParentContainer)。你有什么想法,如何解决在渲染函数内必须使用let counter的问题? - timo
  1. 应该将 this.ParentContainer 大写吗:是的
  2. 有什么想法可以解决在 render 函数中必须使用 let 计数器的问题吗?我已经尝试在给定的 fiddle 中删除它。
- Jatin Parmar
也许我看错了你的示例代码版本,但我指的是第49-61行。最好将它们移到 render 函数之外。也许这超出了原始问题的范围。 - timo
对于计数器部分的解决方案在有很多“sections”时效率低下。我想保持函数的通用性。从问题中可以看出:“(在实际代码中,我有大约200个这些元素,因此手动设置它们的数组项号不是有效的方法)。也许在渲染函数内部进行计数才是最有效的方法。 - timo

1

你不应该直接操作DOM,因为React处理的是虚拟DOM。根据React文档,你应该使用Ref forwarding。更多细节请阅读此文


你能提供一个 Fiddle 吗?另外,你能提供一些关于我在问题中提到的使用引用的信息吗?“...但我认为应该尽量减少引用,正如所述,实际代码由数百个这样的部分组成…” - timo

1
您可以使用ReactDOMfindDOMNode()方法,如果您找不到其他解决方案,但是文档中说:

findDOMNode是一种逃生舱,用于访问底层DOM节点。在大多数情况下,不建议使用此逃生舱,因为它会穿透组件抽象。

例如,我想分享我的应用程序的一个用例,在该用例中,我需要引用容器DOM div以实现我正在使用的库的拖放。

示例:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接