react-material-ui-carousel 라이브러리를 사용해 넣어놓았던 메인페이지, 문제 목록 페이지의 캐러셀을 직접 구현하기로 했다. 이유는 다음과 같다.
번들 포비아에서 분석한 package.json 번들 사이즈 내림차순 정렬 결과 2위인 react-material-ui-carousel
슬라이드 애니메이션을 넣으니 마지막 idx -> 첫번째 idx로 이동할 때 이어지는 것처럼 보이지 않았다. 한번에 x축이 + 100%씩 이동하다가 갑자기 -200% 이동했기 때문이다.
-> 눈속임으로 마지막에 첫번째 내용과 동일한 슬라이드를 하나 추가하고, 마지막 슬라이드로 넘어가면 잠깐 transition을 멈춰놓고 몰래 첫번째 슬라이드로 바꿔치기한 후 다시 transition을 넣어주는 방식으로 해결 가능.
아래와 같이 현재 슬라이드 인덱스, transition 들어갔는지 여부 두가지 상태를 가지고 구현해볼 수 있다.
const [currIdx, setCurrIdx] = useState(1);
const [transition, setTransition] = useState(true);
다음 슬라이드로 넘어갈때 setCurrIdx를 호출해 인덱스를 설정해준다. 범위를 넘어갔을 때의 예외처리와 함께 앞서 언급한 transition on/off 로직을 구현하면 된다. 여기서 replaceSlide가 해당 로직인데 아래에 분리해놓았다.
const moveSlide = (offset: number) => {
let nextIdx = currIdx + offset;
setCurrIdx(nextIdx);
if (nextIdx <= 0) {
nextIdx = items.current.length - 1;
replaceSlide(nextIdx);
} else if (nextIdx >= items.current.length - 1) {
nextIdx = 0;
replaceSlide(nextIdx);
}
setTransition(true);
};
일단 슬라이드를 옮기고 나서 실제로 바꾸고자 하는 위치로 몰래 바꿔치기 하겠다는 뜻. (ex. 마지막 슬라이드에 도착했다면 0번으로 바꾸겠다.)
const replaceSlide = (idx: number) => {
setTimeout(() => {
setCurrIdx(idx);
setTransition(false);
}, 700);
};
react에서는 state가 바뀌면 앱도 리렌더링이 되기 때문에 setInterval이 무한루프에 빠지게 된다. 이것을 해결하기 위해 Dan Abramov님의 해결책을 읽어볼 수 있으며 결론은 다음과 같은 custom hook이다.
import { useState, useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
이는 callback 데이터가 바뀔 때마다 useEffect()가 실행되어 savedCallback의 current 값이 새로운 callback 데이터로 업데이트되도록 구현된 hook이다.
이렇게 직접 Carousel을 구현해볼 수 있다.