Next.js + TypeScript + Swiper 라이브러리로 개발을 진행하다
간단한데 의외로 시간을 많이 잡아먹은 react로 swiper destroy 후 다시 reinit 하기😵
스택오버플로우에서도 의외로 자료 찾기가 힘들었어서 혹시나 참고하실 분이 있을까 적어 봅니다😹
사용한 라이브러리 정보는 다음과 같습니다.
"next": "13.4.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"styled-components": "^5.3.10",
"swiper": "^9.4.0",
"typescript": "5.1.3"
구현하고자 한 기능은 특정 브라우저 창 크기가 특정 사이즈 미만으로 내려가면 swiper를 제거하고(destroy), 다시 특정 사이즈 이상이 되면 swiper를 재 활성화하는(reinit) 기능이었습니다.
// page.tsx
'use client'
import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react';
import type { Swiper as SwiperType } from 'swiper'; // 타입스크립트 타입 지정을 위한 import
import SwiperInit from 'swiper'; // 이름이 겹치므로 Swiper를 SwiperInit 으로 바꾸어서 import
import { useEffect, useRef } from 'react';
import theme from '@/utils/Theme'; // styled-components theme 파일
import info from "@/components/discography/info" // map 돌릴 데이터
import Discography from '@/components/discography/DiscographyStyle'; // styled-components 스타일 객체
import useResize from '@/utils/useResize';
import 'swiper/css'; // swiper 기본 스타일시트 파일
사용할 라이브러리, 타입들을 import 해 줍니다.
타입스크립트 사용 시에는 타입도 추가로 import해 주어야 하고 컴포넌트도 가져와야 하고 후에 재활성화를 위한 생성자도 가져와야 하는데
셋 다 디폴트 이름이 Swiper 여서 그대로 가져올 수가 없습니다.
import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react';
import type { Swiper as SwiperType } from 'swiper'; // 타입스크립트 타입 지정을 위한 import
import SwiperInit from 'swiper'; // 이름이 겹치므로 Swiper를 SwiperInit 으로 바꾸어서 import
그래서 다음과 같이 이름을 바꾸어서 가져왔습니다.
브라우저 창 크기 감지를 위한 useResize
커스텀 훅을 import 했는데 해당 훅의 코드는
https://velog.io/@kimbangul/React-TypeScript-useResize-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%9B%85-%EB%A7%8C%EB%93%A4%EA%B8%B0 와 동일합니다 🙇
// page.tsx
const swiperRef = useRef<SwiperType | null>(null);
const size = useResize();
//...(중략)
const swiperOption : SwiperProps = {
spaceBetween: 50,
slidesPerView:1,
centeredSlides: true,
loop: true,
breakpoints:{
[theme.screenSize.tb]:{
slidesPerView:2,
centeredSlides: false,
},
[theme.screenSize.pc]:{
slidesPerView:3,
centeredSlides: true,
},
[theme.screenSize["pc-l"]]:{
slidesPerView:4,
centeredSlides: false,
}
},
onInit:(swiper)=> {
swiperRef.current = swiper}
}
// breakpoints 안 객체 키 값은 styled-components 의 theme에서 정의해 준
// 사이즈를 그대로 사용하기 위한 변수입니다.
// const screenSize = {
// mb: 500,
// tb: 768,
// pc: 1080,
// "pc-l": 1200
// }
}
Swiper 옵션으로 사용될 객체를 선언합니다.
옵션 타입은 'swiper/react' 에서 가져온 SwiperProps
타입이고, 후에 destroy
, init
메서드 사용을 위해
초기화 시(onInit) useRef
로 해당 swiper를 선택해 주었습니다.
// page.tsx
// ...(중략)
return (
<>
<Discography.Title>Discography</Discography.Title>
<Discography.Container>
<Swiper {...swiperOption}> // 만들어 준 옵션 객체를 spread 연산자로 전개
{
// 데이터 단순히 map()으로 순회해서 나열해주는 부분
info.map((el, idx) => {
return (
// 슬라이드로 사용할 부분은 SwiperSlide 컴포넌트 안에 담아줍니다.
<SwiperSlide key={`album${idx}`}>
<Discography.Item />
<Discography.Text.Container>
<Discography.Text.Title>{el.title}</Discography.Text.Title>
<Discography.Text.Release>{el.release}</Discography.Text.Release>
</Discography.Text.Container>
</SwiperSlide>
)
})
}
</Swiper>
</Discography.Container>
</>
)
뷰를 리턴해 주는 내용은 무척 간단합니다 👀
'swiper/react' 에서 가져온 Swiper
컴포넌트 안에 슬라이드할 내용을 SwiperSlide
로 감싸 넣어주면 됩니다.
// page.tsx
const swiperRef = useRef<SwiperType | null>(null);
const size = useResize();
useEffect(()=>{
// DOM 계산 전 size.width가 undefined
// 타입에러 방지를 위해 사용
if (!size.width) return;
if(size.width < theme.screenSize.mb) {
// 브라우저 창 크기가 지정한 사이즈 미만일 때
swiperRef.current?.destroy(true, true);
// 매개변수는 Swiper 인스턴스 파괴 여부, 사용자 지정 스타일 파괴 여부를 의미
// https://swiperjs.com/swiper-api 의 swiper.destroy() 부분 참고!
}
else if (size.width >= theme.screenSize.mb && swiperRef.current?.destroyed){
// 브라우저 창 크기가 지정 사이즈 이상이고 swiper가 해제되었을 때
swiperRef.current = new SwiperInit('.swiper', swiperOption);
}
}, [size]);
사이즈가 변할 때마다 swiper 제거 및 초기화를 실행하기 위해 useEffect
를 이용해 줍니다. 이 useEffect는 의존성 배열의 매개변수로 useResize
훅에서 반환하는 결과값 size
를 가집니다.
// page.tsx
'use client'
import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react';
import type { Swiper as SwiperType } from 'swiper';
import SwiperInit from 'swiper';
import { useEffect, useRef } from 'react';
import theme from '@/utils/Theme';
import info from "@/components/discography/info"
import Discography from '@/components/discography/DiscographyStyle';
import useResize from '@/utils/useResize';
import 'swiper/css';
export default function DiscographyView() {
const swiperRef = useRef<SwiperType | null>(null);
const size = useResize();
const swiperOption : SwiperProps = {
spaceBetween: 50,
slidesPerView:1,
centeredSlides: true,
loop: true,
breakpoints:{
[theme.screenSize.tb]:{
slidesPerView:2,
centeredSlides: false,
},
[theme.screenSize.pc]:{
slidesPerView:3,
centeredSlides: true,
},
[theme.screenSize["pc-l"]]:{
slidesPerView:4,
centeredSlides: true,
}
},
onInit:(swiper)=> {
swiperRef.current = swiper}
}
useEffect(()=>{
if (!size.width) return;
if(size.width < theme.screenSize.mb) {
swiperRef.current?.destroy(true, true);
}
else if (size.width >= theme.screenSize.mb && swiperRef .current?.destroyed){
swiperRef.current = new SwiperInit('.swiper', swiperOption);
}
}, [size]);
return (
<>
<Discography.Title>Discography</Discography.Title>
<Discography.Container>
<Swiper {...swiperOption}>
{
info.map((el, idx) => {
return (
<SwiperSlide key={`album${idx}`}>
<Discography.Item />
<Discography.Text.Container>
<Discography.Text.Title>{el.title}</Discography.Text.Title>
<Discography.Text.Release>{el.release}</Discography.Text.Release>
</Discography.Text.Container>
</SwiperSlide>
)
})
}
</Swiper>
</Discography.Container>
</>
)
}
복잡하거나 어려운 것도 없었는데, swiper-react 컴포넌트 사용과 타입 지정때문에 시간을 오래 잡아먹었던... 😹😹