필자가 토이 프로젝트를 진행 중에 마주한 고민이다.
useRef를 통해서 특정 요소 클릭 시 특정 이미지로 스크롤이 이동하도록 구현하고자 했다.
이 때, 이미지가 5개 사용되었고, 따라서 useRef가 5개 필요했다.
많은 양이 아니기 때문에 하나하나 useRef로 선언해줬는데 이는 하드코딩으로 굉장히 비효율적이다.
만약, 보여주고싶은 이미지가 100개가 되버린다면 어떻게 될까?
하나하나 useRef를 선언해 줘야할까? 그런 일은 없어야겠다...
이번에는 useRef를 효율적으로 사용할 수 있는 법을 알아보자.
문제 상황에 있는 코드는 아래와 같다.
// import 생략 //
function ArtistPage() {
const artistRef = useRef<HTMLDivElement>(null);
const two = useRef<HTMLDivElement>(null);
const three = useRef<HTMLDivElement>(null);
const four = useRef<HTMLDivElement>(null);
const five = useRef<HTMLDivElement>(null);
const refArr = [artistRef, two, three, four, five];
const moveToArtist = (index: number) => {
refArr[index]?.current?.scrollIntoView({
behavior: "smooth",
block: "center",
});
};
return (
<Layout>
<ArtistsList moveToArtist={moveToArtist} />
<article className="px-3 flex flex-col xl:px-[50px]">
{artistArr.map(
(item, index) => (
<div
className="mb-8 flex flex-col items-center lg:odd:items-end lg:even:items-start"
key={index}
ref={refArr[index]}
>
<Artist src={item.src} name={item.name} />
</div>
)}
</article>
</Layout>
);
}
export default ArtistPage;
img(Artist 컴포넌트)에 스크롤을 이동시키기 위해 부모 컴포넌트인 div에 ref를 달아놓은 것을 확인 할 수 있다.
배열처럼 활용하고자 했던 것까지는 좋은데, 과정은 배열을 활용하는 것이 아니었다.
결국엔 하나하나 useRef를 만들어주었고, 이는 보여줄 요소가 늘어나는 순간부터 굉장히 비효율적인 방법이 될 것이다.
따라서, 이들을 한번에 선언할 수 있는 방법을 찾아보았다.
// import 생략 //
function ArtistPage() {
const artistRef = useRef<any>([]);
const moveToArtist = (index: number) => {
artistRef.current[index]?.scrollIntoView({
behavior: "smooth",
block: "center",
});
};
return (
<Layout>
<ArtistsList moveToArtist={moveToArtist} />
<article className="px-3 flex flex-col xl:px-[50px]">
{artistArr.map(
(item, index) => (
<div
className="mb-8 flex flex-col items-center lg:odd:items-end lg:even:items-start"
key={index}
ref={(element) => {
artistRef.current[index] = element;
}}
>
<Artist src={item.src} name={item.name} />
</div>
)
)}
</article>
</Layout>
);
}
export default ArtistPage;
이전 코드에서는 useRef를 5개나 만들었던 것과 비교했을 때, 현재 코드에서는 useRef를 한번만 사용하였다.
const artistRef = useRef<any>([]);
여기까지만 사용하면 의미가 없다.
이전 코드에서 div 태그에 ref를 달았을 때와 비교했을 때, 현재 코드의 ref에는 아래와 같이 작성되어있다.
ref={(element) => {
artistRef.current[index] = element;
}}
map() 메서드의 index를 사용하여, 호출되는 순서에 맞춰서 ref를 생성해준 것이다.
이렇게 하면 useRef를 일일이 선언하지 않아도 map()과 섞어서 사용하는 것으로 코드를 획기적으로 줄일 수 있다!
moveToArtist 함수도 변화가 있는데, useRef 자체를 여러번 선언해서 배열로 만든 것을 활용하는게 아니라, useRef를 하나만 만들고 current를 사용하여 배열을 만든 것이므로, 인덱스를 사용하는 코드가 약간 변경되었다.(이는 이 게시물에서 중요한 부분은 아니므로, 그렇구나 하고 넘어가주시면 감사하겠습니다 ㅎㅎ)
useRef를 선언하는 다른 방법도 있다.
const artistArr = [만들어놓은 배열];
const artistRef = useRef(new Array(artistArr.length));
이렇게 선언해도 전혀 문제없이 작동한다.
필자가 Typescript를 사용하기 전 공부할 때, any를 쓰면 Typescript를 쓰는 의미가 없다고 쓰여있는 글을 본 기억이 있다.
그래서 필자도 웬만하면 any를 사용하지 않으려고 합니다.
그러면 위에서 작성한 코드를 any를 사용하지 않고 쓰는 법은 없을까?
있다! 아래와 같이 작성하면 된다.
const artistRef = useRef<null[] | HTMLDivElement[]>([]);
만약 null을 작성하지 않거나, null 옆에 배열 기호를 제거하고 작성하면
'HTMLDivElement | null' 형식은 'HTMLDivElement' 형식에 할당할 수 없습니다.
'null' 형식은 'HTMLDivElement' 형식에 할당할 수 없습니다.ts(2322)
라는 오류를 artistRef.current를 사용하는 부분에서 마주하게 될 것이다. 조심!