在React中循环使用useRef来引用元素

13

使用React,我有一个静态声明的ref列表:

  let line1 = useRef(null);
  let line2 = useRef(null);
  let line3 = useRef(null);
  ...
  //IN MY RENDER PART
  <li ref={(el) => (line1 = el)}>line1</li>
  <li ref={(el) => (line2 = el)}>line1</li>
  <li ref={(el) => (line3 = el)}>line1</li>

然后将引用传递给动画函数,一切正常运作; 现在情况有些改变,我使用map创建了列表项,并且无法正确地引用该元素; 我尝试了类似以下的内容:

{menu.menu.map((D) => {
let newRef = createRef();
                    LiRefs.push(newRef);
                    return (
                      <li
                        key={D.id}
                        ref={(el) => (newRef = el)} >
                        {D.label}
                      </li>
                    );
                  })}

但我传递给动画函数的数组为空(我猜是因为该函数在useEffect钩子内被调用,而LiRefs还不是useRef)。我也知道我将创建多少个

  • ,所以我可以在开始时声明它们,并使用类似于以下的引用

    但我传递给动画函数的数组为空(我猜是因为该函数在useEffect钩子内被调用,而LiRefs还不是useRef)。我也知道我将创建多少个<li>,所以我可以在开始时声明它们,并使用类似于以下的引用

    ref={(el) => (`line${i}` = el)}
    

    有没有其他的解决方法?这个似乎不起作用。


    any other solution i could try?

    R:

    有没有其他的解决方法?这个似乎不起作用。


  • 你可能想要使用React.useRef()而不是React.createRef()createRef()只用于React类组件中。 - jered
    1
    然而,在循环内部调用 任何 钩子技术上违反了“钩子规则”。如果您绝对、肯定、确定知道您的“菜单”数组的长度不会改变,那么您可以这样做,但请小心谨慎。 - jered
    1
    @jered React.createRef 可以在任何需要创建 react ref 的地方使用,我不知道有什么规定说它们只能在基于类的组件中使用。如果是这样的话,那么 React 文档 肯定会明确说明。 - Drew Reese
    4个回答

    22

    问题

    每次渲染时,当menu被映射时,它会创建新的React refs,这将无法正常工作。

    解决方案

    使用一个ref来保存生成的refs数组,并在映射时分配它们。

    const lineRefs = React.useRef([]);
    
    lineRefs.current = menu.menu.map((_, i) => lineRefs.current[i] ?? createRef());
    

    稍后在映射用户界面时,将存储在 lineRefs 中的 React ref 附加到索引 i 上。

    {menu.menu.map((D, i) => {
      return (
        <li
          key={D.id}
          ref={lineRefs.current[i]} // <-- attach stored ref
          {D.label}
        </li>
      );
    })}
    

    抱怨关于 ?? 是 && 还是 ||? - Raul H
    2
    @RaulH 从技术上讲,两者都不是。但在这种情况下,Nullish Coalescing Operator (??) 更像逻辑或(||)。要点在于,如果左侧 (LHS) 是 nullundefined,则返回右侧(RHS )。而对于 || 来说,如果 LHS 是任何 falsy 值,则返回 RHS。 - Drew Reese
    我不理解这个答案,有人可以更详细地解释语法吗? - Hugo
    @Hugo 你需要帮助的特定部分是什么?你想更详细了解哪种语法?唯一“奇怪”或新的可能是上面提到的Nullish Coalescing Operator,其余都是相当标准的React Javascript代码。 - Drew Reese
    1
    这个回答非常有帮助,因为我不知道可以使用 createRef 而不是 useRef。请注意,在某些情况下,根本不需要使用 useRef([]),特别是如果引用数组是从状态或属性中的数组 map 出来的。 - kevlarr

    9
    我使用React Hooks版本。
    使用useMemo创建引用数组以提高性能。
    const vars = ['a', 'b'];
    const childRefs = React.useMemo(
        () => vars.map(()=> React.createRef()), 
        [vars.join(',')]
    );
    
    

    React将每个ref挂载到childRefs

    {vars.map((v, i) => {
        return (
            <div>
                 <Child v={v} ref={childRefs[i]} />
                 <button onClick={() => showAlert(i)}> click {i}</button>
            </div>
        )
     })
    }
    

    这里有一个可行的演示,希望能够帮助。^_^

    
    <div data-lang="js" data-hide="false" data-console="false" data-babel="true" class="snippet">
    <div class="snippet-code">
    <pre class="snippet-code-js lang-js prettyprint-override"><code>const Child = React.forwardRef((props, ref) => {
    
      React.useImperativeHandle(ref, () => ({
        showAlert() {
          window.alert("Alert from Child: " + props.v);
        }
      }));
    
      return <h1>Hi, {props.v}</h1>;
    });
    
    const App = () => {
      const vars = ['a', 'b'];
      const childRefs = React.useMemo(
        () => vars.map(()=> React.createRef()), 
        // maybe vars.length
        [vars.join(',')]
      );
      function showAlert(index) {
        childRefs[index].current.showAlert();
      }
      
      return (
        <div>
          {
            vars.map((v, i) => {
              return (
                <div>
                  <Child v={v} ref={childRefs[i]} />
                  <button onClick={() => showAlert(i)}> click {i}</button>
                </div>
              )
            })
          }
        </div>
      )
    }
    
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(
      <App />,
      rootElement
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
    
    <div id="root"></div>


    7

    其他答案中可能存在一些TypeScript不一致和复杂性。因此,我认为在循环中使用useRef钩子的最佳方法是:

    // Declaration
    const myRef = useRef([]);
    myRef.current = [];
    const addToRefs: (el) => void = (el) => {
      if (el && !myRef.current.includes(el)) {
        myRef.current.push(el);
      }
    };
    

    And

    // Assignment (input element example)
    ...
    ...
    {anyArrayForLoop.map((item, index) => {
       return (
           <input
               key={index}
               ref={addToRefs}
           />
       );
    })}
    ...
    ...
    

    最终结果:

    // The Data
    myRef.current
    

    简单明了,你救了我的命。谢谢。 - user16841471

    1

    不必将引用存储在数组中,可以在循环内为每个组件创建一个引用。 您还可以通过函数在父组件中访问该引用。

    您可以类似于这样做。

    const { useRef, useState } = React;
    
    const someArrayToMapStuff = ["a", "b", "c"];
    
    const ListWithRef = ({ text, setDisplayWhatsInsideRef }) => {
    
      const ref = React.useRef(null);
      
      const logAndDisplayInnerHTML = () => {
        setDisplayWhatsInsideRef(ref.current.innerHTML);
        console.log(ref.current);
      };
      
      return (
        <li 
          ref={ref}
          onClick={logAndDisplayInnerHTML}
        >
          <button>{text}</button>
        </li>
      );
      
    };
    
    const App = () => {
    
      const [displayWhatsInsideRef, setDisplayWhatsInsideRef] = useState("");
    
      return (
        <ul>
          {someArrayToMapStuff.map(thing => <ListWithRef 
            key={thing} 
            text={thing} 
            setDisplayWhatsInsideRef={setDisplayWhatsInsideRef}
          />)}
          
          {displayWhatsInsideRef && (
            <h1>Look, I'm a ref displaying innerHTML: {displayWhatsInsideRef}</h1>
          )}
        </ul>
      );
    };
    
    ReactDOM.createRoot(
        document.getElementById("root")
    ).render(<App />);
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

    希望这能帮助到某人。

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