web-dev-qa-db-ja.com

マップのreact useRefでDOMをターゲットにする方法

React useRef()フックを使用してDOM要素の配列を取得するソリューションを探しています。

例:

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

どうすればこれを達成できますか?

25
BrownBe

useRefは、Reactのrefcurrentのフィールドのみを持つオブジェクトの構造)に部分的に類似しています。

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フックでアクセスします。理由:refsは要素がレンダリングされた後にバインドされるため、レンダリング中に初めて実行されるときに、まだ初期化されていません。

26
skyboyer

skyboyer's answer について少し詳しく説明します。パフォーマンスを最適化するため(および潜在的な奇妙なバグを回避するため)、useMemoではなくuseRefを使用することをお勧めします。 useMemoは値ではなく引数としてコールバックを受け入れるため、_React.createRef_は、最初のレンダリング後に1回だけ初期化されます。コールバック内では、createRef値の配列を返し、その配列を適切に使用できます。

初期化:

_  const refs= useMemo(
    () => Array.from({ length: 3 }).map(() => createRef()),
    []
  );
_

ここで(2番目の引数として)空の配列は、Reactに参照を1回だけ初期化するように指示します。参照カウントが変更された場合、_[x.length]_を "deps配列"として渡して動的に参照を作成する必要があります: Array.from({ length: x.length }).map(() => createRef()),

用途:

_  refs[i+1 % 3].current.focus();
_
3
beqa

配列の長さが事前にわかっている場合は、例でそれを行い、参照の配列を作成して、インデックスごとにそれぞれに割り当てることができます。

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>
  )
}
0
dotconnor