Как целевой DOM реагирует на использование useRef в карте - PullRequest
0 голосов
/ 01 марта 2019

Я ищу решение о получении массива элементов DOM с реакцией useRef() hook.

пример:

const Component = () => 
{

  // In `items`, I would like to get an array of DOM element
  let items = useRef(null);

  return <ul>
    {['left', 'right'].map((el, i) =>
      <li key={i} ref={items} children={el} />
    )}
  </ul>
}

Как этого добиться?

Ответы [ 2 ]

0 голосов
/ 11 марта 2019

useRef лишь частично похож на ref в React (просто структура объекта с единственным полем current). Хук

useRef нацелен на сохранение некоторых данных между рендерами и их изменениеданные не вызывают повторную визуализацию (в отличие от useState).

Также просто мягкое напоминание: лучше избегать инициализации ловушек в циклах или if.Это первое правило хуков .

Имея это в виду, мы:

  1. создаем массив и сохраняем его между рендерами на useRef
  2. мы инициализируем каждый элемент массива с помощью createRef()
  3. мы можем ссылаться на список, используя .current нотацию

    const Component = () => {
    
      let refs = useRef([React.createRef(), React.createRef()]);
    
      useEffect(() => {
        refs.current[0].current.focus()
      }, []);
    
      return (<ul>
        {['left', 'right'].map((el, i) =>
          <li key={i}><input ref={refs.current[i]} value={el} /></li>
        )}
      </ul>)
    }
    

Это можно было безопасно изменитьмассив (скажем, путем изменения его длины).Но не забывайте, что изменяющиеся данные, хранящиеся в useRef, не вызывают повторную визуализацию.Таким образом, чтобы изменить длину для повторного рендеринга, нам нужно задействовать useState.

const Component = () => {

  const [length, setLength] = useState(2);
  const refs = useRef([React.createRef(), React.createRef()]);

  function updateLength({ target: { value }}) {
    setLength(value);
    refs.current = refs.current.splice(0, value);
    for(let i = 0; i< value; i++) {
      refs.current[i] = refs.current[i] || React.createRef();
    }
    refs.current = refs.current.map((item) => item || React.createRef());
  }

  useEffect(() => {
   refs.current[refs.current.length - 1].current.focus()
  }, [length]);

  return (<>
    <ul>
    {refs.current.map((el, i) =>
      <li key={i}><input ref={refs.current[i]} value={i} /></li>
    )}
  </ul>
  <input value={refs.current.length} type="number" onChange={updateLength} />
  </>)
}

Также не пытайтесь получить доступ к refs.current[0].current при первом рендеринге - это вызовет ошибку.

Скажите

      return (<ul>
        {['left', 'right'].map((el, i) =>
          <li key={i}>
            <input ref={refs.current[i]} value={el} />
            {refs.current[i].current.value}</li> // cannot read property `value` of undefined
        )}
      </ul>)

Таким образом, вы либо охраняете его как

      return (<ul>
        {['left', 'right'].map((el, i) =>
          <li key={i}>
            <input ref={refs.current[i]} value={el} />
            {refs.current[i].current && refs.current[i].current.value}</li> // cannot read property `value` of undefined
        )}
      </ul>)

, либо получаете доступ к useEffect крючку.Причина: ref s связаны после рендеринга элемента, поэтому при первом запуске рендеринга он еще не инициализирован.

0 голосов
/ 01 марта 2019

Если вы заранее знаете длину массива, то, что вы делаете в своем примере, вы можете просто создать массив ссылок, а затем назначить каждый из них по их индексу:

const Component = () => {
  const items = Array.from({length: 2}, a => useRef(null));
  return (
    <ul>
      {['left', 'right'].map((el, i)) => (
        <li key={el} ref={items[i]}>{el}</li>
      )}
    </ul>
  )
}
...