Reactの関数コンポーネントにおいて、refを複数扱う方法は、以下のような書き方がよく知られていると思います。
const ref = useRef<RefObject<HTMLDivElement>[]>([]);
data.forEach((_, index) => {
ref.current[index] = React.createRef<HTMLDivElement>();
});
これとは異なる方法で複数のrefを扱う方法を発見したので、ここに残しておきます。
再帰コンポーネント
再帰コンポーネントを利用すると、createRefを使わずとも複数の要素を扱うことができます。
useRefとforwardRefを組み合わせて使う方法については、私の以前の記事を御覧ください。
const NewMultiRefSample = React.forwardRef<HTMLDivElement[], { data: string[] }>(({ data }, ref) => {
// 今回表示する要素のref
const divRef = useRef<HTMLDivElement>(null);
// 次以降表示する要素のref
const nextRef = useRef<HTMLDivElement[]>(null);
// 今回表示する要素と、次以降表示する要素を合わせてフォワーディングする
useImperativeHandle(ref, () => {
const divDom = divRef.current;
const nextDoms = nextRef.current;
if (divDom != null && nextDoms != null) {
return [divDom, ...nextDoms];
} else {
return [];
}
});
const copiedData = data.concat();
if (copiedData.length <= 0) {
return null;
}
const headData = copiedData.shift();
return (
<>
<div ref={divRef}>{headData}</div>
<NewMultiRefSample ref={nextRef} data={copiedData} />
</>
);
});
このコンポーネントにrefを渡すと、DOMノードを配列を得られます。
const App = () => {
const data = ["red", "blue", "yellow"];
const refs = useRef<HTMLDivElement[]>(null);
useEffect(() => {
console.log(refs.current);
});
return <NewMultiRefSample ref={refs} data={data} />;
};
この手法の長所
この手法の長所は、親コンポーネント(サンプルのApp)と子コンポーネント(サンプルのNewMultiRefSample)の再レンダリングを切り離すことができることです。
冒頭に紹介した方法では、子コンポーネントをメモ化していたとしても、親コンポーネントが再レンダリングされるたびに、createRefで作り直されたrefが子コンポーネントに渡されてしまうため、子コンポーネントは必ず再レンダリングされてしまいました。
それに対して、この手法はuseRefしか利用しないため、親コンポーネントが再レンダリングされても子コンポーネントには同じrefが渡される、つまり子コンポーネントは再レンダリングされません。
親コンポーネントが頻繁に再レンダリングされる、または子コンポーネントのレンダリングのコストが重い場合、この手法を使うのが良いと思います。
この手法の短所
この手法の短所は、なんといっても読みにくい・理解されにくいことです。
チーム開発の場合は、むやみに利用するのは避けたほうがいいでしょう。