useRef для элемента в цикле в реакции

Используя React, у меня есть список ссылок, статически объявленных следующим образом:

  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>

затем ссылки передаются в функцию анимации, и все работает правильно; теперь все немного изменилось, и я создаю элемент списка с помощью карты, и я больше не могу правильно ссылаться на элемент; я пробовал что-то вроде:

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

but the array i pass to the animation function is empty (i guess because the function is called inside useEffect hook and LiRefs is not yet a useRef) i also know the number of

  • i will create, so i can declare them at the beginning and the reference with something like

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

    который не работает, какое-либо другое решение, которое я мог бы попробовать?


  • person popeating    schedule 18.12.2020    source источник
    comment
    Вы, вероятно, хотите React.useRef(), а не React.createRef(). createRef() предназначен только для использования в компонентах класса React.   -  person jered    schedule 18.12.2020
    comment
    Однако вызов любого хука внутри цикла технически противоречит правилам хуков. Если вы абсолютно точно знаете, что длина вашего массива меню не изменится, вы можете это сделать, но действуйте осторожно.   -  person jered    schedule 18.12.2020
    comment
    @jered React.createRef можно использовать везде, где вы хотите / вам нужно создать ссылку на реакцию, я не знаю правила, которое гласило бы, что они могут использоваться только в компонентах на основе классов. Если бы это было так, то наверняка в документации React это было бы ясно. .   -  person Drew Reese    schedule 18.12.2020


    Ответы (2)


    Проблема

    Это не сработает, поскольку каждый рендер при отображении menu создает новые ссылки на реакцию.

    Решение

    Используйте ссылку для хранения массива сгенерированных ссылок и назначьте их при сопоставлении.

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

    позже при отображении пользовательского интерфейса прикрепите ссылку на реакцию, хранящуюся в lineRefs по индексу i

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

    Моя версия React Hooks.

    useMemo для создания массива ссылок для повышения производительности.

    const vars = ['a', 'b'];
    const childRefs = React.useMemo(
        () => vars.map(()=> React.createRef()), 
        [vars.join(',')]
    );
    
    

    React смонтирует каждую ссылку на childRefs

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

    Вот работающая демонстрация, надеюсь, что это поможет. ^ _ ^

    
    
    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>
    person Ace    schedule 20.01.2021