ํฐ์คํ ๋ฆฌ ๋ทฐ
Swiper ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ ํต์ ์ด ๋๋ CarouselSlider ๊ธฐ๋ฅ ๊ตฌํ_useLayoutEffect ์ฌ์ฉ
choi95 2022. 1. 26. 17:53Issue
Swiper React Components
Swiper is the most modern free mobile touch slider with hardware accelerated transitions and amazing native behavior.
swiperjs.com
ํ์ฌ ํ๋ก์ ํธ์์ ์ฌ์ฉ์์๊ฒ ํ๋ก์ฐํ ๋งํ ์น๊ตฌ๋ฅผ ์๋จ ๋ฐ์์ ๋ณด์ฌ์ฃผ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
์ด๋ ์น๊ตฌ ๋ฆฌ์คํธ๋ฅผ ํ ๋ฒ์ ๋ชจ๋ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด ์๋๋ผ ์ด๊ธฐ์ 8๋ช ์ Friend Figure๋ง ๋ณด์ฌ์ฃผ๊ณ ์ฌ์ฉ์๊ฐ ๋ฒํผ์ ํด๋ฆญํ์์ ๊ฒฝ์ฐ์ ํํด์, 8๊ฐ์ ๋ฐฐ์๋ก ์ ๋์ ์ผ๋ก ์ฆ๊ฐํ๋ offset์ query๊ฐ์ผ๋ก ์ค์ ํ์ฌ ์๋ฒ์ API ํต์ ์ ํ๋ค.
Carousel Slider ๊ธฐ๋ฅ์ ์ง๊ธ๊น์ง ์ฌํ ๋ค๋ฅธ ํ๋ก์ ํธ์์ ์ด๋ ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด ํ๋ ์ฝ๋ฉํ ๊ฒฝํ์ด ๋ง์์๊ธฐ ๋๋ฌธ์ ์ด๋ฒ์๋ Carousel Slider ๊ธฐ๋ฅ์ ๊ตฌํํ ๋ ๋ํ์ ์ผ๋ก ๋ง์ด ์ฐ์ด๋ Swiper ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด ๋ณด๊ธฐ๋ก ํ์๋ค.
Swiper React ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ค์ด๋ก๋ํ๊ณ ์ฌ์ ์ค์ ํ๋ ๋ฐฉ๋ฒ์ ์ธํฐ๋ท์ ์ด๋ฏธ ๋ง๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ์๋ตํ๊ณ ํด๋น ํ๋ก์ ํธ์์ ์ค์ค๋ก ๊ณ ๋ฏผํ๋ฉฐ ๋ง์ด๊ทธ๋ ์ด์ ํ ์ฝ๋๋ฅผ ์ค์ ์ ์ผ๋ก ๊ธฐ๋กํด๋ณด๊ณ ์ ํ๋ค.
Code
์ฐ์ ์ฌ๋ผ์ด๋ ๊ธฐ๋ฅ๋ง ๋ด๋นํ๊ณ ์๋ CarouselSlider ์ปดํฌ๋ํธ๋ฅผ ๋ณ๋๋ก ์์ฑํ์ฌ Presentation Component ๊ฐ์ ๊ด์ฌ์ฌ์ ๋ถ๋ฆฌ๋ฅผ ์ฑ๋ฆฝ์์ผ ์ฃผ์๋ค.
ํ์ฌ ํ๋ก์ ํธ์์ ์น๊ตฌ ๋ฆฌ์คํธ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๊ธฐ๋ฅ ๋์์ด ์ํํ๊ฒ ๋๊ธฐ ์ํด์ Swiper ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ ๊ณตํ๋ Swiper Component๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ๋ฉด ์๋๋ค.
์ฐ์ , ํ์ฌ ํ๋ก์ ํธ์์์ ์น๊ตฌ ๋ฆฌ์คํธ๋ ๋์ด ์กด์ฌํ์ง ์๋๋ค. ์ฆ, DB์ ์ ์ฅ๋ ์น๊ตฌ ๋ฆฌ์คํธ๋ค์ offset๊ฐ์ผ๋ก ๋งค๋ฒ next ๋ฒํผ์ ํด๋ฆญํ ๋๋ง๋ค API์ ํต์ ํ์ฌ ๊ฐ์ ธ์์ ๊ธฐ์กด ๋ฆฌ์คํธ ๋ฐฐ์ด์ ์ด์ด์ ์ ์ฅํด์ค์ผ ํ๋ค.
์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์์ ๋ ์ด๋ ๋ฏ ์ถ๊ฐ์ ์ธ ๊ธฐ๋ฅ ๋์์ ํ๊ธฐ ์ํด์๋ ๋ณ๋์ swiper ๋ณ์๋ฅผ ์์ ์ ์ปดํฌ๋ํธ ๋ด์ state ๊ฐ์ผ๋ก ์ค์ ํด์ฃผ๊ณ ํด๋น ๋ณ์๋ฅผ Swiper ์ปดํฌ๋ํธ ๋ด props๋ก ๋๊ฒจ swiper๋ฅผ ์ธ์คํด์ค ํ์์ผ๋ก ์ฌ์ฉํ ์ ์๋๋ก ์ค์ ํด์ผ ํ๋ค.
์๋์ ๊ฐ์ด useState์ setSwiper ํจ์ ๊ฐ์ฒด๋ฅผ useRef๋ฅผ ์ฌ์ฉํ์ฌ ๋ ธ๋ ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ ๋์ฒ๋ผ ์ค์ ํด ์ค์ผ๋ก์จ, Swiper ์ปดํฌ๋ํธ์ ์ข ์๋์ง ์๊ณ swiper ์ธ์คํด์ค์ ๋ฉ์๋๋ค์ ์ฌ์ฉํ์ฌ ์ํ๋ ๋์์ด ์ผ์ด๋๋๋ก ์ปค์คํฐ ๋ง์ด์งํด์ฃผ์๋ค.
( ... )
const [swiper, setSwiper] = useState(null);
( ... )
return (
<StyledSwiper ref={setSwiper}>
{children}
</StyledSwiper>
);
์์ ์ค์ ๋ง์ผ๋ก ์ํํ ๋์์ด ๋์ง ์๋๋ค. Swiper ์ธ์คํด์ค๋ฅผ ๋ด๋นํด ์ค ๊ฐ์ฒด๋ฅผ ๋ฐ์ธ๋ฉ์์ผ์ฃผ์๋ค๋ฉด, ์ด์ ๋ ์ด๋ฅผ ์ค์ ์ค์ ๊ฐ ๊ฐ์ฒด์ ์ ๋ณด๋ก ๋ฐ์ธ๋ฉํด์ฃผ์ด ๋ง์ฐฌ๊ฐ์ง๋ก props๋ก ๋๊ฒจ์ฃผ์ด์ผ ํ๋ค.ํด๋น ํ๋ก์ ํธ์์ Carousel Slider์ ๊ธฐ๋ณธ ๋์์ธ์ ์ฐจ์ฉํ์ง ์๊ณ ๋์์ด๋ ๋ถ์ด ์ฃผ์ ์์๋๋ก ์คํ์ผ ์ปค์คํฐ ๋ง์ด์งํด์ผ ๋๊ธฐ ๋๋ฌธ์ ๋ฒํผ ์์ ๋ํ ๋ค์๊ณผ ๊ฐ์ด ๋ด๊ฐ ์ค์ ํ ๊ฐ์ผ๋ก ๋๋๋ง๋๋๋ก ์ฝ๋๋ฅผ ์์ฑํ์๋ค.
( ... )
const navigationPrevRef = useRef(null);
const navigationNextRef = useRef(null);
( ... )
const swiperParams = {
navigation: {
prevEl: navigationPrevRef.current,
nextEl: navigationNextRef.current,
},
onBeforeInit: swiper => {
swiper.params.navigation.prevEl = navigationPrevRef.current;
swiper.params.navigation.nextEl = navigationNextRef.current;
swiper.navigation.update();
},
slidesPerView: 1,
onSwiper: setSwiper,
};
์ด์ next ๋ฒํผ์ ํด๋ฆญํ์ ๊ฒฝ์ฐ์ ์๋ฒ์์ ์๋ก์ด ์น๊ตฌ ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ธ์์ผ ํ๊ธฐ ๋๋ฌธ์ ๋ค์๊ณผ ๊ฐ์ด react-redux useDispatch์ friends Module์์ offset๊ฐ์ ์ฆ๊ฐ์์ผ ์ฃผ๋ increaseFriendOffset๊ณผ ์น๊ตฌ ๋ฆฌ์คํธ๋ฅผ ์๋ฒ์์ ๊ฐ์ ธ ์ค๋ friendList ๋ ์ก์ ์์ฑ ํจ์๋ฅผ importํ์ฌ ์ด๋ฒคํธ ์ฝ๋ฐฑ ํจ์์์ trigger ์์ผ ์ฃผ์๋ค.
์ด๋ ์ถ๊ฐ์ ์ผ๋ก ์กฐ๊ฑด ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์๋๋ฐ, ๋ง์ฝ์ next ๋ฒํผ์ ๋๋ฌ API ํต์ ์ ํจ๊ณผ ๋์์ ๋ค์ ์ฌ๋ผ์ด๋๋ก ๋์ด๊ฐ๊ณ ๋ค์ ๋ค๋ก ๋์๊ฐ ๊ฒฝ์ฐ์๋ ๋ณ๋์ API ํต์ ์ ํ์ง ์๊ณ ์ ์ ์๋ฒ๋ฅผ ํตํด ๊ฐ์ ธ ์จ ๋ฐ์ดํฐ ๋ฆฌ์คํธ๋ฅผ ๊ทธ๋๋ก ์ฌ์ฌ์ฉํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ Click ์ด๋ฒคํธ๊ฐ ์ผ์ด๋๋๋ผ๋ ํด๋น ๋ฒํผ prev ๋ฒํผ์ผ ๊ฒฝ์ฐ์๋ ๋ฆฌํด๋๋๋ก ํด์ฃผ์๋ค.
๋ํ ์ด์ ์ฌ๋ผ์ด๋๋ก ๋์๊ฐ๋ค๊ฐ ๋ค์ ๋ค์ ์ฌ๋ผ์ด๋๋ก ์ด๋ํ์๊ณ ํด๋น ์ด๋๋ ์ฌ๋ผ์ด๋๊ฐ ๋ง์ง๋ง ์ฌ๋ผ์ด๋์ผ ๊ฒฝ์ฐ์๋ swiper.isEnd ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํ์ฌ ๋ง์ง๋ง ์ฌ๋ผ์ด๋์ธ์ง ์๋ณํ๊ณ ๋ง๋ค๋ฉด isPrev ์๋ณ ๋ณ์๋ฅผ false๋ก ๋ฐ๊ฟ์ฃผ์๋ค.
const nextSlide = () => {
if (isPrev) {
if (swiper.isEnd) isPrev = false;
return;
}
batch(() => {
dispatch(increaseFriendsOffset("recommendFriendList"));
dispatch(friendList());
});
};
useLayoutEffect๋ฅผ ํตํด ์ฌ๋ผ์ด๋ ์ด๋
๋ฒํผ์ ํด๋ฆญํ๊ณ API ํต์ ์ ์๋ฃํ์ฌ ๊ทธ ๋ค์ ์น๊ตฌ ๋ฆฌ์คํธ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ ๋ฆฌ๋์ค ์คํ ์ด์ ์ ์ฌํ๋ ๊ฒ๊น์ง ์ฑ๊ณตํ์์ง๋ง ํด๋น ๊ฐ์ useEffect ๋ด์์ ์ฒ๋ฆฌํ๋ค ๋ณด๋ ์ ๋๋ก ๋์์ด ๋์ง ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์๋ค.
ํ์ฌ CarouselSlider ์ปดํฌ๋ํธ์ ์์ ์ปดํฌ๋ํธ๋ ์น๊ตฌ ๋ฆฌ์คํธ๋ฅผ ๋ด๊ณ ์๋ ๋ฐฐ์ด์ ํตํด map ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ณ ์์ผ๋ฉฐ, ๊ทธ๋ ๊ฒ ์์ฑ ๋ JSX๋ children ๊ฐ์ฒด ํํ๋ก ๋๊ฒจ์ค๋ค.
๊ทธ๋ฆฌ๊ณ ํด๋น children ๊ฐ์ฒด๋ฅผ useEffect ๋ด์ ์์กด์ฑ ๋ฐฐ์ด์ ํ ๋นํด ์ฃผ์ด ํด๋น ๊ฐ์ด ๋ฐ๋ ๊ฒฝ์ฐ์ ์ฌ๋ผ์ด๋๊ฐ ์ค์ ํ๋ฉด ์์์ ๋ค์ ์ด์ด ๋ถ์ด์ง๊ณผ ๋์์ ๋ค์์ผ๋ก ๋์ด๊ฐ ์ ์๋๋ก ์๋ํ ๊ฒ์ด์๋๋ฐ, ๊ธฐ๋ฅ์ด ์ ๋๋ก ๋์๋์ง ์์๋ค.
( ... )
return (
<Container>
<Name>๋ง์ถค์น๊ตฌ ์ถ์ฒ</Name>
<CarouselSlider>
{completeContents[0].map((friendList, index) => {
return (
<SwiperSlide key={index}>
<FriendList>
{friendList.map(
({ id, nickname, image_url, follows }, index) => {
return (
<FriendFigure
key={index}
nickname={nickname}
image_url={image_url}
follows={follows}
/>
);
}
)}
</FriendList>
</SwiperSlide>
);
})}
</CarouselSlider>
</Container>
);
์์ธ์ ๊ณฐ๊ณฐํ ์๊ฐํด๋ณด๋ useEffect์ ๊ฐ์ ๊ฒฝ์ฐ์๋ Rendering๊ณผ Paint๊ฐ ๋ชจ๋ ์ผ์ด๋ ๋ค์ ๋น๋๊ธฐ์ ์ผ๋ก ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ์๋กญ๊ฒ ์ถ๊ฐ ๋ ์ฌ๋ผ์ด๋๊ฐ ์์ ์ ์ผ๋ก ์ฐ๊ฒฐ๋์ง ์๊ณ ์ด๋ก ์ธํด ๋ค์ ์ฌ๋ผ์ด๋๋ก Switching๋์ง๋ ์์๋ ๊ฒ์ด๋ค.
์ด์ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ์ฐพ์๋ณด๋ useLayoutEffect ๋ผ๋ Hooks๋ฅผ ์ฐพ์ ์ ์์๋ค.
https://ko.reactjs.org/docs/hooks-reference.html#uselayouteffect
Hooks API Reference โ React
A JavaScript library for building user interfaces
ko.reactjs.org
The signature is identical to useEffect, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
์ฆ, ํ๋ฉด์ ์ต์ข ์ ์ผ๋ก ๋ณด์ฌ์ง๊ธฐ ์ ์ DOM ํธ๋ฆฌ๊ฐ ๊ตฌ์ฑ๋๊ณ ํด๋น DOM ํธ๋ฆฌ์ ๋ ์ด์์์ ์ฐธ์กฐํ์ฌ ๋๊ธฐ์ ์ผ๋ก ๋ฆฌ๋๋๋ง์ด ํ์ํ ๊ฒฝ์ฐ์ ์ธ ์ ์๋ Hooks ์๋ค.
์ด์ ๋ค์๊ณผ ๊ฐ์ด children์ ํด๋นํ๋ ์น๊ตฌ ๋ฆฌ์คํธ ๋ ์ด์์์ด ๋ชจ๋ ๋๋๋ง๋ ์ดํ์ ๋ค์ ์ฌ๋ผ์ด๋๊ฐ ๋์ด๊ฐ์ง๋๋ก ์ฝ๋๋ฅผ ์์ฑํ์๊ณ ๊ธฐ๋ฅ ๋์์ ์ฑ๊ณตํ์๋ค.
useLayoutEffect(() => {
if (swiper) swiper.slideNext();
}, [children]);
๋์ ์์
