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>
}
どうすればこれを達成できますか?
useRef
は、Reactのref
(current
のフィールドのみを持つオブジェクトの構造)に部分的に類似しています。
useRef
フックは、レンダリング間で一部のデータを保存し、そのデータを変更しても再レンダリングをトリガーしないことを目的としています(useState
とは異なります)。
また、穏やかなリマインダーです。ループまたはif
の初期化フックを回避することをお勧めします。 フックの最初のルール です。
これを念頭に置いて、次のことを行います。
useRef
によってレンダー間で保持しますcreateRef()
で初期化します.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は要素がレンダリングされた後にバインドされるため、レンダリング中に初めて実行されるときに、まだ初期化されていません。
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();
_
配列の長さが事前にわかっている場合は、例でそれを行い、参照の配列を作成して、インデックスごとにそれぞれに割り当てることができます。
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>
)
}