슬라이스쇼를 지원하는 라이브러리들은 많았지만, 일단 내 손으로 직접 기능을 구현하는 것을 목표로 하는 프로젝트였기에 슬라이스쇼 기능도 직접 구현해보았다.
완전 처음에는 참고자료 없이 직접 구현해보려고 했으나 아무리 고민을 해봐도 흐릿한, 추상적인 구조 이외에는 다음 단계로 넘어가질 못해서 결국 이곳저곳 자료를 찾아서 구현하였다.
슬라이드쇼에 사용될 이미지 파일은 결국 눈에 보이지만 않을 뿐 컴포넌트에 포함되어 있다. 특정 슬라이드만 화면에 출력되야하고, 다른 슬라이드로 이동하는 기능까지 포함하면 슬라이스쇼의 기능은 얼추 다 구현한 것이다.
// 전체 슬라이드의 길이.
const slideLength = 5;
// 몇 번째 슬라이드가 출력되야하는지 제어.
const [currentSlide, setCurrentSlide] = useState(0);
// 슬라이드의 src 주소를 제어.
const [slideImgSrc, setSlideImgSrc] = useState('');
// 슬라이드를 이동시키는 함수, 동작할 때마다 index의 값을 체크.
const showSlide = (index) => {
const listLength = slideLength - 1;
if (index < 0) {
setCurrentSlide(listLength);
return;
}
else if (index > listLength) {
setCurrentSlide(0);
return;
};
if (index <= listLength) {
setCurrentSlide(index);
return;
};
};
useEffect(() => {
showSlide(0);
}, []);
슬라이스쇼 구현을 위해서는 전체 슬라이드의 길이의 값, 현재 화면에 출력해야하는 index의 값, 슬라이드에 사용될 src 주소값이 필요하다.
또한 슬라이드를 이동시킬 때, 어느 슬라이드로 이동하라는 값이 내려왔는데 이것이 전체 슬라이드의 길이를 초과하거나 0 이하로 내려가는 경우해주어야 한다. 이를 위해서 함수 내부에서 index의 값을 체크하여 조건에 걸렸을 때 특정 index를 setCurrentSlide의 인자로 보내 호출하도록 하였다.
index가 0 미만이다. 처음 슬라이드에서 이전 슬라이드로 갈 경우이니 마지막 슬라이드가 출력되도록 한다.
index가 listLength 초과이다. 마지막 슬라이드에서 다음 슬라이드로 넘어가는 경우이니 처음 슬라이드가 출력되도록 한다.
index가 0 이상이고 listLength 이하이다. 아무 문제가 없으니 setCurrentSlide 인자에 index 값을 그대로 사용해준다.
이후 currentSlide 값을 디펜던시로 하는 useEffect Hooks를 이용하여 switch-case 문을 사용해 currentSlide의 값에 맞춰 적절한 src 주소값이 적용되도록 구현하였다.
useEffect(() => {
switch (currentSlide) {
case 0:
setSlideImgSrc(productData.slide1Img);
break;
case 1:
setSlideImgSrc(productData.slide2Img);
break;
(...)
default:
break;
}
// eslint-disable-next-line
}, [currentSlide]);
슬라이스쇼의 좌우에 버튼 UI를 하나씩 만들어 놓고 미리 제작한 showSlide 함수를 onClick 속성에 사용해준다. 버튼 이동시 슬라이드 index를 체크하는 기능은 이미 포함되어 있으니 잘 작동한다.
<SildeButton src={leftarrow} alt='' onClick={() => { showSlide(currentSlide - 1) }} />
<SildeButton src={rightarrow} alt='' onClick={() => { showSlide(currentSlide + 1) }} />
슬라이드쇼 하단에 존재하며, 버튼을 클릭할 경우 특정 슬라이드로 바로 이동하는 기능. 이거 명칭을 뭐라고 하는지 정확하게 모르겠다.. 슬라이드 포인터?
이 기능은 UI와 기능 구현은 별 일이 아니었는데.. 현재 슬라이드에 해당하는 포인터가 강조되도록 하는 시각적 기능을 구현하는 효율적인 방법이 떠오르지 않았다. 고민 끝에 결국..
const [isActive1, setIsActive1] = useState(false);
const [isActive2, setIsActive2] = useState(false);
const [isActive3, setIsActive3] = useState(false);
const [isActive4, setIsActive4] = useState(false);
const [isActive5, setIsActive5] = useState(false);
(...)
useEffect(() => {
switch (currentSlide) {
case 0:
setSlideImgSrc(productData.slide1Img);
setIsActive1(true);
setIsActive2(false);
setIsActive3(false);
setIsActive4(false);
setIsActive5(false);
break;
case 1:
setSlideImgSrc(productData.slide2Img);
setIsActive1(false);
setIsActive2(true);
setIsActive3(false);
setIsActive4(false);
setIsActive5(false);
break;
(...)
default:
break;
}
// eslint-disable-next-line
}, [currentSlide]);
(...)
<PointerDots>
{isActive1 ? <PointerDot isActive={isActive1} onClick={() => { setSlide(0) }} /> : <PointerDot isActive={false} onClick={() => { setSlide(0) }} />}
{isActive2 ? <PointerDot isActive={isActive2} onClick={() => { setSlide(1) }} /> : <PointerDot isActive={false} onClick={() => { setSlide(1) }} />}
{isActive3 ? <PointerDot isActive={isActive3} onClick={() => { setSlide(2) }} /> : <PointerDot isActive={false} onClick={() => { setSlide(2) }} />}
{isActive4 ? <PointerDot isActive={isActive4} onClick={() => { setSlide(3) }} /> : <PointerDot isActive={false} onClick={() => { setSlide(3) }} />}
{isActive5 ? <PointerDot isActive={isActive5} onClick={() => { setSlide(4) }} /> : <PointerDot isActive={false} onClick={() => { setSlide(4) }} />}
</PointerDots>
각각의 포인터를 제어하는 플래그 변수를 만들어놓고 currentSlide가 변할 때마다 모든 플래그 변수를 조정하는 단순무식한 방법을 사용하였다. UI쪽에서는 이렇게 조정되는 플래그 변수를 이용해서 isActive가 true인 경우에는 포인터가 하얀색으로, false인 경우에는 회색으로 바뀌도록 구현하였다. 이렇게 하니까 잘 되긴 하는데.. 아무리 봐도 코드가 너무 비효율적이다.. 개선 방법을 고민해봐야 한다.
// 특정 슬라이드로 이동하는 함수. 이전 혹은 다음 슬라이드 이동 기능과 몇 번 슬라이드로 바로 이동하는 기능 모두룰 포함.
const setSlide = (slideNumber) => {
setCurrentSlide(slideNumber);
showSlide(slideNumber);
};
게다가 나중에 알아차린 사실인데, showSlide 함수를 만들어놓고 동일한 동작을 하는 setSlide 함수를 또 만들어놨다. 이전/다음 슬라이드 이동 기능과 특정 슬라이드 이동 기능이 다르다고 생각해서 저지른 실수인데, 정작 showSlide 함수를 만들어 놓을때 특정 슬라이드로 이동하는 것도 가능하게 만들어놓고 이런 어이없는 짓을 저질렀다..
평가 방법, 개인적인 코드 리뷰 및 Chat GPT 사용.
-> 최적화? 선언한 useState가 너무 많다. 컴포넌트가 렌더링 될 때마다 이들 모두가 죄다 재선언된다는 것인데 성능상 문제가 될 수 밖에 없다. 사실 내가 리액트 최적화에 대한 깊이있는 지식이 부족해서 useMemo나 useCallback을 정확하게 어떻게 사용해야하는지 헷갈려하는것도 문제이다. 이 부분에 대해서는 정말 잘 공부해 볼 것!
Chat GPT의 힘을 빌려본 결과 다음과 같은 피드백을 받았다.
슬라이드 포인터: 슬라이드 포인터의 활성화 여부를 isActive1, isActive2와 같이 여러 개의 상태 변수로 관리하는 대신, 슬라이드 번호와 현재 슬라이드 번호를 비교하여 활성화 여부를 결정하는 방법을 사용하면 더 효율적일 수 있습니다.
최적화: 컴포넌트 렌더링 최적화를 위해 React.memo나 useMemo, useCallback을 사용하여 불필요한 렌더링을 방지하고, 효율적인 컴포넌트 업데이트를 할 수 있도록 하면 성능을 향상시킬 수 있습니다.
추가로, 컴포넌트의 재사용성과 유지보수성을 고려하여 코드를 구조화하는 것도 중요합니다. 작은 단위로 분리된 함수나 컴포넌트로 구성하면 코드를 더 쉽게 이해하고 유지보수할 수 있습니다.
이 내용을 숙지해 둘 것! 이번 프로젝트에서 얻은 교훈들은 모두 다음 프로젝트에서 귀중하게 사용될 것들이다.
훌륭한 글이네요. 감사합니다.