[React] slide API 없이 슬라이드 구현하기

hongduhyeon·2022년 4월 3일
0
post-thumbnail
이거 하다가 맥북 반대로 몇번이나 접을 뻔했넹 ^_^
ㅅ...사랑해 내 맥북...

slide API에게 언제까지나 의존 할 수 없다.

라고 했지만 진짜 slide API의 중요성을 너무나도 느끼는 일주일이었다. (물론 작업기간 3일)

언제나 이론상으로 완벽한 코드지만 npm start와 새로고침 한번한번이 이렇게 무서웠던 적은 처음이다. 오류가 무서운게 아니라 이 문제가 해결되면 다음엔 어떻게 로직을 연결시키지? 라는게 제일 컸던 것 같다. (쫄보)

갑자기 뜬금없지만 성장한 점이 꽤 있는 듯 하다.

?

일단 useRef를 사용해보고 useState와 useEffect를 한번에 사용해본 점이 가장 크지 않나 싶다. 각각 hook들이 뭘하는지 어떤 장점을 갖고 있고 어떻게 사용해야하는지 확실히 개념을 잡지 않고 갔을 땐 코드를 쓰면서 점점 더 막막해졌는데 한번의 터닝 포인트를 갖고 천천히 다시 해보는 기회가 가장 큰 성장 포인트였던 것 같다.

그럼 거두절미하고 내가 좋아하는 어디 내놔도 부끄러운 내 코드를 보러가자.

1. 구조 잡기 (로직)

음... 뭐부터 해야할까 하다가 슬라이드 API 없이 했을 때 슬라이드의 기본적인 기능을 봤다.

  1. 초마다 자동으로 넘어가는 기능
  2. pagination
  3. 마지막 슬라이드에서 다시 처음으로 오는 기능

과거의 홍두현은 크게 이 세가지를 구현해보기로 했고 하나씩 천천히(?) 구현 해보면서 서로 체이닝을 하기로 했다.

누구나 다 그럴싸한 계획을 갖고 있다. 쳐 맞기 전까지는
- 마이클 타이슨 -

2. 슬라이드 리스트 목 데이터로 받기

[
  {
    "id": 1,
    "src": "../images/slide-img01.jpg",
    "text": "첫번째 슬라이드 버튼",
    "alt": "첫번째 슬라이드"
  },
  {
    "id": 2,
    "src": "../images/slide-img02.jpg",
    "text": "두번째 슬라이드 버튼",
    "alt": "두번째 슬라이드"
  },
  {
    "id": 3,
    "src": "../images/slide-img03.jpg",
    "text": "세번째 슬라이드 버튼",
    "alt": "세번째 슬라이드"
  },
  {
    "id": 4,
    "src": "../images/slide-img04.jpg",
    "text": "네번째 슬라이드 버튼",
    "alt": "네번째 슬라이드"
  },
  {
    "id": 5,
    "src": "../images/slide-img05.jpg",
    "text": "다섯번째 슬라이드 버튼",
    "alt": "다섯번째 슬라이드"
  }
]

총 5개의 이미지 슬라이드를 구현하려고 해서 고유한 id값, 이미지 주소 src, 이미지 alt값, 버튼의 text 값을 살며시 준비해봤다.

3. 이미지 슬라이드 map()로 다 보여주기

// Slide.js
import React from 'react';
import './slide.scss';

const Slide = ({ count, slideList, slideRef, handleSlider }) => {
  return (
    <>
      <ul ref={slideRef} className="slideWrap">
        {slideList.map(slide => (
          <li key={slide.id}>
            <img src={slide.src} alt={slide.alt} />
          </li>
        ))}
      </ul>
      <div className="inner">
        <div className="pagination">
          {slideList.map(button => (
            <button
              type="button"
              key={button.id}
              onClick={() => {
                handleSlider(button.id);
              }}
              className={button.id === count ? 'active' : ''}
            >
              {button.text}
            </button>
          ))}
        </div>
      </div>
    </>
  );
};

export default Slide;

이 컴포넌트에서 슬라이드와 pagination 둘 다 작업을 했다. (나누려고 했지만 안에 내용이 5줄 ㅋ...)
받아온 목데이터를 상위 부모요소에서 props로 slideList를 받아오고 그걸 map으로 돌려서 이미지 슬라이드 부분에 id, src, alt 값을 차례로 넣어줬다. (유진님.. alt값 꼭 넣을게요....)

그리고 pagination 또한 slideList에 데이터가 있는 만큼 보여야하고 생성이 되야하기 때문에 slideList를 한번 더 map을 돌려줬다. 이렇게 <Slide> 컴포넌트 만들기 완성.

아 까먹고 넘어갈 뻔 했는데 ul에 useRef는 이 글을 참고해보자.
과거의 홍두현이 간단히 정리한 useRef

4. 대망의 Main 컴포넌트 슬라이드 작업

난이도 최상, 극악
내가 난생 구글링을 이렇게 해본 적이 없다... 그래도 자존심 지키겠다고 남들이 한거 그대로 가져오는 행위는 하기 싫어서 천천히 생각을 해봤다. 🧐

음....

  1. 매초마다 돌아가야한다.
  2. 그럼 매초가 지날때 스타일값을 줘야한다.
  3. 그 스타일 값이 pagination도 먹어야한다.

1번부터 천천히 시작해보자.

4-1. setTimeout, clearTimeout

import React, { useEffect, useRef, useState } from 'react';

const Main = () => {
  const slideRef = useRef();
  const [count, setCount] = useState(1);
  const [slideList, setSlideList] = useState([]);

  useEffect(() => {
    const interval = setTimeout(() => {
      setCount(() => {
        if (count < slideList.length) {
          setCount(count + 1);
        } else {
          setCount(1);
        }
      });

      handleSlider(count);

      return () => clearTimeout(interval);
    }, 6000);
  });
  
  const handleSlider = count => {
    if (count === 5) {
      slideRef.current.style.transform = 'translateX(0)';
    } else {
      slideRef.current.style.transform = `translateX(-${
        window.innerWidth * count
      }px)`;
    }
  };
  
  return (
    <main className="main">
      <div className="mainSlide">
        <Slide
          slideRef={slideRef}
          count={count}
          slideList={slideList}
          handleCount={handleCount}
          handleSlider={handleSlider}
        />
      </div>
    </main>
  );
};

export default Main;

setTimeout으로 내가 설정한 시간마다 안에 const interval에 있는 내용들을 반복해줘야한다. 내가 봤을땐 6초가 적당한 것 같아서 6000을 줬고 그 안에 내용을 봐보자.

setCount로 계속 증가되는 count의 역할을 수행중인 useState를 하나 만들어서 몇 초가 지나면 1씩 올라가 대신 특정한 시점에 count가 1로 가야해 라는 동작을 할 수 있게끔 만들어준다. 그 구문이 useEffect 안에 있는 if문이고 슬라이드 데이터의 length값을 기준으로 작업했다.

그럼 count가 올라갔다가 다시 1로 오는걸 확인했다. 하지만 useEffect에서 setTimeout을 사용할 때 꼭 써줘야하는게 clearTimeout이다. 만약 interval을 끝내지 않는다면 오류는 안나오지만 고장난 레이아웃을 볼 수 있다. 꼭 써주자.

4-2. useRef를 이용한 스타일값 변경

나에게 극강의 사이다를 선사한 useRef에게 먼저 박수를 치고 가겠다.
useRef로 translateX값을 움직여야하는 <ul>에게 ref={slideRef}를 선언해주고 함수로 작성해서 파라미터 값을 받을 수 있게 만들어준다.

  useEffect(() => {
    const interval = setTimeout(() => {
      setCount(() => {
        if (count < slideList.length) {
          setCount(count + 1);
        } else {
          setCount(1);
        }
      });

      handleSlider(count);

      return () => clearTimeout(interval);
    }, 6000);
  });

  const handleSlider = count => {
    if (count === 5) {
      slideRef.current.style.transform = 'translateX(0)';
    } else {
      slideRef.current.style.transform = `translateX(-${
        window.innerWidth * count
      }px)`;
    }
  };

handleSlider에 count를 인자로 받고, 인자로 받은 파라미터를 사용했고 sildeRef의 optional을 이용해서 style까지 접근을 했고, 내가 바꿔줘야할 값은 translateX이다.

그냥 값을 넣어넣고 else if() 문을 이용해서 가독성이 안좋게 작업을 해줄 수도 있었지만 Templete Literal을 사용했다.

window.innerWidth를 넣은 이유는 렌더링이 됐을 때 화면이 노트북일지 데스크탑의 4k 모니터일지 아무도 모른다. 그렇기 때문에 윈도우 화면의 너비값과 파라미터로 받은 count를 계속 곱해줬고 if문에 작성한대로 count 값을 엄격하게 비교해서 5가 되면 translateX(0)으로 가게 만들어줬다.

그리고 마지막으로 만든 이 함수를 어떻게 내 코드에 녹여낼까... 생각을 해봤는데 의외로 답은 간단하게 나왔다.

아까 만든 useEffect 안에 setTimeout으로 계속 올라가는 값이 있다.
계속 올라가는 값을 handleSlider에 파라미터로 넣어준다면 ?
내가 하던 고민들은 모두 해결이 된다.

후기

제일 많이 든 생각은 이게 되나 ? 라는 생각이었다. 하지만 이게 됐고 직접 정한 목표까지 구현을 해봄으로써 많은 도움이 됐다. 구글링을 하면서 남들의 코드를 이해가 안되더라도 욕심만으로 카피해서 코드를 쓸 수도 있었다. 하지만 이번에 제일 많이 느낀 점은 내가 생각한대로 코드를 천천히 작성해보고 거기서 얻는 지식과 개념이 다음 기능 구현에도 큰 도움이 된다는걸 느꼈다. 항상 말하지만 기본기가 제일 중요하다고 생각하는 사람 중 한명이 아마 내가 아닐까 싶다.

profile
마음이 시키는 프론트엔드

0개의 댓글