TIL no.63 - React Slide 만들기(feat.Custom Hook)

김종진·2021년 7월 24일
0

React

목록 보기
15/17

React Slide

회사 plugin 작업중 슬라이드 모션을 구현하는 작업을 하게 되었는데

상단에 카테고리 컴포넌트가 있고 각 카테고리를 클릭하면 양 옆 슬라이드 모션으로 컨텐츠가 나오는데 만약 현재 카테고리에서 오른쪽의 카테고리를 누르면 화면이 오른쪽으로 한번만 움직이면서 바로 누른 카테고리 컨텐츠가 나오도록 해야했다.

이는 Slick을 활용하기에는 조금 번거로울 듯 싶어 직접 구현해 보기로 하였다.

구현 연습 영상

구현 코드

SlideContainer.js

카테고리와 컴포넌트와 슬라이드 될 컨텐츠 컴포넌트 2개로 구성하였다.

const SlideContainer = () => {

    const [currIdx, setCurrIdx] = useState(null);
    const [selectedCategory , setSelectedCategory] = useState(null);

    const onClickCategory = (list, idx) => {
        setCurrIdx(idx);
        setSelectedCategory(list);
    }

    return (
        <div className="Root">
            <Category onClickCategory={onClickCategory}/>
            <Slide currIdx={currIdx} selectedCategory={selectedCategory}/>
        </div>
    );
};

export default SlideContainer;

Slide.js

import React, { useEffect, useRef, useState } from 'react';
import { usePrevious } from '../../hooks/common';
import "./Slide.scss";

const Slide = ({currIdx, selectedCategory}) => {

    const firstRef = useRef();
    const secondRef = useRef();
    const thirdRef = useRef();
    const [firstChildren, setFirstChildren] = useState(null);
    const [secondChildren, setSecondChildren] = useState(null);
    const [thirdChildren, setThirdChildren] = useState(null);
    const prevIdx = usePrevious(currIdx)
    const prevCategory = usePrevious(selectedCategory)

    
    useEffect(() => {
        const firstListEl = firstRef.current;
        const secondListEl = secondRef.current;
        const thirdListEl = thirdRef.current;

        if(prevIdx < currIdx) {
            // moveRight
            const previousEl = (<img src={prevImg} alt="img"/>)
            const selectedEl = (<img src={selectedImg} alt="img"/>)

            const center = document.getElementsByClassName('currentProduct')[0];
            const next = document.getElementsByClassName('nextProduct')[0];
            const previous = document.getElementsByClassName('previousProduct')[0];

            center.classList.remove('currentProduct');
            next.classList.remove('nextProduct');
            previous.classList.remove('previousProduct');

            center.classList.add('previousProduct');
            next.classList.add('currentProduct');
            previous.classList.add('nextProduct');

            center.style.transition = "transform 1s";
            next.style.transition = "transform 1s";
            previous.style.transition = "transform 1s";

            previous.style.transition = 'none'

            if (firstListEl.className.includes('previousProduct')) {
                setFirstChildren(previousEl);
            } else if (secondListEl.className.includes('previousProduct')) {
                setSecondChildren(previousEl);
            } else if (thirdListEl.className.includes('previousProduct')) {
                setThirdChildren(previousEl);
            }

            if (firstListEl.className.includes('currentProduct')) {
                setFirstChildren(selectedEl);
            } else if (secondListEl.className.includes('currentProduct')) {
                setSecondChildren(selectedEl);
            } else if (thirdListEl.className.includes('currentProduct')) {
                setThirdChildren(selectedEl);
            }
            
        } else if(prevIdx > currIdx) {
            // moveLeft
            const previousEl = (<img src={prevImg} alt="img"/>)
            const selectedEl = (<img src={selectedImg} alt="img"/>)

            const center = document.getElementsByClassName('currentProduct')[0];
            const next = document.getElementsByClassName('nextProduct')[0];
            const previous = document.getElementsByClassName('previousProduct')[0];

            center.classList.remove('currentProduct');
            next.classList.remove('nextProduct');
            previous.classList.remove('previousProduct');

            center.classList.add('nextProduct');
            next.classList.add('previousProduct');
            previous.classList.add('currentProduct');

            center.style.transition = "transform 1s";
            next.style.transition = "transform 1s";
            previous.style.transition = "transform 1s";

            next.style.transition = 'none'

            
            if (firstListEl.className.includes('previousProduct')) {
                setFirstChildren(previousEl);
            } else if (secondListEl.className.includes('previousProduct')) {
                setSecondChildren(previousEl);
            } else if (thirdListEl.className.includes('previousProduct')) {
                setThirdChildren(previousEl);
            }

            if (firstListEl.className.includes('currentProduct')) {
                setFirstChildren(selectedEl);
            } else if (secondListEl.className.includes('currentProduct')) {
                setSecondChildren(selectedEl);
            } else if (thirdListEl.className.includes('currentProduct')) {
                setThirdChildren(selectedEl);
            }
        }
    }, [currIdx])
    return (
        <div className="slide">
            <div ref={firstRef} className="previousProduct">
                {firstChildren}
            </div>
            <div ref={secondRef} className="currentProduct">
                {secondChildren ? secondChildren : <img src={HTML} alt="img" /> }
            </div>
            <div ref={thirdRef} className="nextProduct">
                {thirdChildren}
            </div>
        </div>
    );
};

export default Slide;

왼쪽 가운데 오른쪽 슬라이드를 div를 만들고 안에 들어갈 컨텐츠를 state 값이 들어가도록 했다.

현재 categoryIdx와 클릭한 categoryIdx를 비교하여 슬라이드의 방향을 적용해주기 위해
prevIdx 변수를 만들어주었는데 이 때 usePrevious 라는 커스텀 훅을 만들어 활용하였다.

custom hook
현재 값이 다음 값으로 바뀔 때 현재 값을 저장해 놓도록 만들었다.

import { useEffect, useRef } from "react";

export function usePrevious(value, defaultValue = undefined) {
    const ref = useRef(defaultValue);

    useEffect(() => {
      ref.current = value;  
    })

    return ref.current
}

슬라이드 모션을 구현하는 부분은 각 className을 가지고 잇는 tag를 찾아 상황에 맞게 className을 변경하고 transition 효과를 주었다.

const center = document.getElementsByClassName('currentProduct')[0];
const next = document.getElementsByClassName('nextProduct')[0];
const previous = document.getElementsByClassName('previousProduct')[0];

center.classList.remove('currentProduct');
next.classList.remove('nextProduct');
previous.classList.remove('previousProduct');

center.classList.add('previousProduct');
next.classList.add('currentProduct');
previous.classList.add('nextProduct');

center.style.transition = "transform 1s";
next.style.transition = "transform 1s";
previous.style.transition = "transform 1s";

previous.style.transition = 'none';

그리고 가장 고민했던 부분이 슬라이드가 넘어갈 때 현재 슬라이드를 유지하면서 다음 컨텐츠를 보여주는 부분이었다.

   const previousEl = (<img src={prevImg} alt="img"/>)
   const selectedEl = (<img src={selectedImg} alt="img"/>)

슬라이드가 움직이면서 넘어가는 화면을 previousEl
슬라이드가 움직이면서 넘어오는 화면을 selectedEl 을 지정해줘서
해당 클래스명을 가진 tag에 setState를 해주어 화면을 적용해준다.

profile
FE Developer

0개의 댓글