[Next.js] Swiper.js destroy 후 reinit 하기

김방울·2023년 6월 14일
2

Next.js

목록 보기
3/7
post-thumbnail

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) 기능이었습니다.

라이브러리 import

// 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 와 동일합니다 🙇

Swiper 옵션 지정

  // 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로 감싸 넣어주면 됩니다.

destroy & init

// 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 컴포넌트 사용과 타입 지정때문에 시간을 오래 잡아먹었던... 😹😹

참고자료

profile
코딩하는 고양이🐱 / UI Developer, Front-end Developer

0개의 댓글