【问题标题】:How do I target DOM elements inside a react component, or should I avoid targeting DOM elements all together?如何在 react 组件中定位 DOM 元素,还是应该避免将 DOM 元素一起定位?
【发布时间】:2020-03-27 21:35:27
【问题描述】:

我创建了一个脚本,该脚本在鼠标悬停在父容器上时激活,并且应该将其子元素从鼠标移开。我目前已经让它工作了,但是代码的某些部分似乎与 REACT 代码的外观相矛盾。尤其是两个部分。

  1. 我在渲染函数中使用了一个计数器,以便每个 span 从 state.customProperties 获取正确的自定义属性,state.customProperties 是一个在父元素鼠标悬停时更新自定义属性的数组。

    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. 在 mousemove 函数中,我使用 document.getElementByIdquerySelectorAll 来获取所有部分元素并将鼠标坐标与部分元素坐标进行比较。

    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 来定位实际组件内部的元素。我觉得我应该能够在不遍历 DOM 的情况下定位它。引用部分元素可能是一种解决方案,但我认为应该将引用保持在最低限度,并且如上所述,实际代码由数百个这些部分组成。

JSFIDDLE

除了更新custom="customProperty(0 0)" 属性之外,代码并没有做更多的atm。您可以通过鼠标悬停时的元素检查器看到这种情况。

我能否在不必计算渲染函数中的&lt;section&gt; 元素且不必使用document.querySelectorAll 的情况下使此功能正常工作?

【问题讨论】:

    标签: reactjs dom


    【解决方案1】:

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

    根据 React 的官方文档,您可以使用 refs 访问 dom 元素。 您必须使用React.createRef() 调用来创建引用。

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

    遍历 dom 以获取特定的 dom 元素对于 React 来说不是一个好的做法,因为它会导致性能问题。但是,react 允许使用createRef() 来做同样的事情。

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

    是的,请考虑执行以下步骤:

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

      this.ParentContainer=React.createRef();
    

    然后在渲染中使用 parentContainer ref:

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

    您可以查看here

    编辑

    关于如何解决必须在渲染函数中使用 let 计数器的任何想法

    你可以像这样在外部渲染定义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 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>
    

    【讨论】:

    • 啊,我想我必须在每个单独的元素上放置一个 ref。不知道我可以通过这样的子元素查询。这对于 DOM 定位部分来说是完美的。(在this.parentContainer btw 上有一个错误,应该是this.ParentContainer 大写)。关于如何解决必须在渲染函数中使用let counter 的任何想法?
    • 1.应该是 this.ParentContainer 大写:是的 2.关于如何解决必须在渲染函数中使用 let 计数器的任何想法?我试图在给定的小提琴上删除它
    • 也许我看错了你的小提琴版本,但我说的是第 49-61 行。最好我会将这些移到渲染函数之外。也许这超出了原始问题的范围。
    • sections很多的时候,计数器部分的解决方案是低效的。我想保持函数的通用性。来自问题:(in the live code I've got about 200 of these elements so setting the array item number for them manually is not efficient). 也许在渲染函数内部计数毕竟是最有效的方法。
    【解决方案2】:

    如果您没有找到其他解决方案但文档中所说的,您可以使用ReactDOM 的方法findDOMNode()

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

    【讨论】:

      【解决方案3】:

      你不应该直接操作 DOM,因为在虚拟 dom 上反应进程。 根据 React 文档,您应该使用 Ref 转发。 更多详情please read this

      【讨论】:

      • 你能提供一个小提琴吗?另外,您能否提供有关我在有关使用参考的问题中陈述的评论的信息? : ""...但我认为引用应该保持在最低限度,并且如上所述,实际代码由数百个这些部分组成..."
      猜你喜欢
      • 2021-05-01
      • 2021-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-27
      • 1970-01-01
      相关资源
      最近更新 更多