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);
};
저는 사실 setInterval을 좀 무서워하는 편입니다. 예상치못한 사이드이펙트가 발생할 여지가 높기 때문인데요, 써야할 때는 또 써야하므로 이번 기회에 써봤습니다. 그런데 react에서는 더더욱 신경써야하는 부분이 있습니다. 바로 state가 바뀌면 App도 리렌더링이 된다는 점입니다. 따라서 setInterval이 무한루프에 빠지게 됩니다. 이것을 해결하기 위해 Dan Abramov님의 멋진 글을 읽어볼 수 있으며 결론은 다음과 같은 custom hook입니다.
import React, { 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을 구현해볼 수 있었습니다 🎉