Kim-DaHam / Portfolly

Portfolly 프로젝트 리팩토링
1 stars 0 forks source link

PortfolioItem 캐러셀 Prev, Next Arrow 버튼 더블 클릭 버그 #9

Closed Kim-DaHam closed 9 months ago

Kim-DaHam commented 9 months ago

Bug Report

개요

PortfolioItem.tsx 컴포넌트의 캐러셀에서 Prev/Next 버튼에 더블 클릭이 적용되지 않는 문제가 발생했습니다. 여러번 빠르게 클릭한 만큼 캐러셀이 빠르게 넘어가지 않습니다.

사용한 캐러셀 라이브러리는 react-slick 입니다.

캐러셀 맨 처음 페이지, 맨 끝 페이지에 도달했을 때 각각 Prev, Next 버튼이 보이지 않도록 만들기 위해 캐러셀의 인덱스를 파악해야 했고, 이를 useState로 관리했습니다.

const [currentSlideIndex, setCurrentSlideIndex] = useState(0);

Prev 버튼 클릭 시 prev - 1 Next 버튼 클릭 시 prev + 1 되어 페이지가 증가/감소합니다.

버튼을 빠르게 클릭한 만큼 currentSlideIndex는 증감하는데, 캐러셀은 덜 넘어가고, 현재 캐러셀 인덱스와 currentSlideIndex가 불일치하는 문제가 발생합니다.


📸 Screenshots

정상 화면 1페이지 2페이지 3페이지
image image image

버그 발생 화면 3 페이지에서 Prev 버튼을 빠르게 2번 클릭하면 index -1 연산이 두 번 수행되어 currentSlideIndex는 0이 됩니다. 따라서 Prev 버튼이 사라집니다.

하지만 캐러셀은 한 페이지만 넘어가느라 아직 2 페이지고, 1페이지로 갈 수 없게됩니다. image


💻 Code

// PortfolioItem.tsx

// 현재 인덱스
const [currentSlideIndex, setCurrentSlideIndex] = useState(0);

// PrevArrow 컴포넌트 onClick 함수
const handlePrevious = (button:HTMLButtonElement) => {
  setCurrentSlideIndex((prev) => prev - 1); // 인덱스 1 감소
  slick?.slickPrev(); // 이전 페이지로 이동
}

// NextArrow 컴포넌트 onClick 함수
const handleNext = (button:HTMLButtonElement) => {
  setCurrentSlideIndex((prev) => prev + 1); // 인덱스 1 증가
  slick?.slickNext(); // 다음 페이지로 이동
}


🙁 Actual behavior

빠르게 클릭하면 currentSlideIndex 만 늘어나고 캐러셀은 클릭한 만큼 넘어가지 않습니다.

🙂 Expected behavior

캐러셀이 넘어가는 타이밍과 currentSlideIndex가 증가하는 타이밍이 일치하면 좋겠습니다.


추가 사항

Kim-DaHam commented 9 months ago

해결 방법

react-slick은 캐러셀이 넘어가는 속도인 speed 값이 기본값 500ms으로 제공되기 때문에 발생한 문제였습니다.

따라서 speed: 0 으로 설정하면 클릭 속도에 맞춰 캐러셀이 넘어갑니다.

하지만 부드러운 움직임을 유지하고 싶을 경우, 캐러셀이 speed 시간 동안 다 움직이고 정지한 다음에 Prev/Next 버튼이 활성화돼야 합니다.

1. lodash의 throttle을 사용하는 방법 🔗 참고 throttle을 사용하여 캐러셀 speed 시간 당 한 번 이벤트가 발생하도록 조정할 수 있습니다.

하지만 현재 코드의 경우, handlePrev()/handleNext() 함수 안에 <Slider>를 참조하는 slick이 존재합니다.

throttle을 쓸 경우 리렌더링 되어도 (과거에 계속 호출된) 자기 자신을 기억하기 위해 useCallback을 쓰고 초기화 옵션으로 [](초기 렌더링 때 한 번 초기화)를 지정해야 합니다.

하지만 throttle 함수가 정의되는 첫 순간에는 아직 DOM이 형성되지 않아 slick이 존재하지 않습니다. 따라서 결국 캐러셀을 넘기지 못하게 되는 겁니다.

2. 직접적으로 핸들러 막기 결국 무식한 방법으로 문제를 해결했습니다.

const [currentSlideIndex, setCurrentSlideIndex] = useState(0); // 현제 캐러셀 인덱스
const [beforeClicked, setBeforeClicked] = useState(false); // 직전에 버튼이 클릭되었는지 여부
const [slick, setSlick] = useState<Slider>(); // 캐러셀 컴포넌트 <Slider>

const handlePrev = ()=> {
  if(beforeClicked) return; // 바로 직전에 버튼 클릭했으면 핸들러 작동 x early return
  setBeforeClicked((prev)=>!prev); // 그게 아니면 버튼이 정상적으로 눌린 게 되고 true로 바꿈
  slick?.slickPrev(); // 이전 페이지로 이동
  setCurrentSlideIndex((prev) => prev - 1); // 인덱스 1 감소
  setTimeout(()=>setBeforeClicked((prev)=>!prev), 200); // 캐러셀이 넘어가는 시간(speed)인 200ms가 지나면 다시 버튼 활성화되도록 false로 바꿔줌
};

const handleNext = ()=> {
  if(beforeClicked) return;
  setBeforeClicked((prev)=>!prev);
  slick?.slickNext();
  setCurrentSlideIndex((prev) => prev + 1);
  setTimeout(()=>setBeforeClicked((prev)=>!prev), 200);
};

결과는 딱 원하는대로 나왔습니다.

Next 버튼을 더블 클릭했을 때, 첫 번째 클릭한 순간 캐러셀 이동, 인덱스 +1 증가. 두 번째 클릭은 반영되지 않습니다.

단점은 해석이 불편한 코드가 되었다는 점 같습니다.

3. 클릭 횟수만큼 빠르게 넘기기가 가능한 캐러셀 라이브러리를 설치하자. 당장 가진 환경에서 문제를 해결하고싶다는 오기로 인해 시도하진 않았지만 가장 현명한 방법 같습니다.