리액트

리액트-route transition animation (2)

ImNM 2022. 8. 11. 15:05
 

리액트-route transition animation (1)

밴드부 티켓 예매를 거의 휴대폰으로 할테니 웹이어도 앱느낌이 나게끔 기획을 했었다. 레이아웃을 휴대폰 레이아웃에 고정시키고 , 일정 범위 이상 화면이 크게되면 , 레이아웃이 더이상 늘어

devnm.tistory.com

  전편을 안보고 오신분들은 전편을 보고오시라.

우선 이글을 통해 얻어갈 수 있는 것들을 알려주겠다.

  • 전역 history 객체를 이용해 history.listen 이벤트를 설정하는 방법
  • 이벤트를 통한 redux에 pathname 을 저장해 , 이전 패스와 비교하는 방법
  • 저장된 pathname을 통해 바뀐값을 비교하여, 애니메이션 방향을 결정하는 방법
  • TransitonGroup 의 childFactory 속성을 이용해서 classNames 속성을 바꿔주는 방법
  • history.state를 이용해 좀더 쉽게 애니메이션 넣는 방법

  뒤로가기 눌렀을땐, 어떻게 해요? 


 문제가 있다.  구현하고자하는 목표는 앞으로넘길땐 오른쪽에서, 뒤로가기했을땐 왼쪽에서 페이지가 넘어가는 방식으로 구현을 해야한다.

한방향으로만 할거면 이전상태의 정보가 필요없지만, 양방향으로 움직일려면 결국 이전상태의 정보가 필요하다.

 

이전 상태의 정보란 이전페이지의 url 정보를 의미하고 브라우저에서는 해당정보를 history 라는것으로 관리를한다.

 

리액트 react-router-dom v6 react context 밖에서 history.push 하기

react-router-dom v6로 업그레이드 되면서 switch 가 routes로 바뀌고... 뭐든 옛날 소스를 절대로 가져다 쓸수 없는 상황이 되었다. 가장 불편했던점은 공식문서나 어디에서도 history 객체를 전역으로 가

devnm.tistory.com

 위글에서 브라우저 history와 react router dom V6에서 히스토리 정보를 react element 바깥에서 접근할수 있는 방법을 기술해 놓았다.

 위와같은 방법으로 history를 밖에서 커스텀하여서 관리를 하게되면 장점이 하나 생기는데 바로 history.listen 과 같이 이벤트를 등록시킬 수 있다는 점이다.

 

전역 history 객체를 이용해 history.listen 이벤트를 설정하기


import { createBrowserHistory } from 'history';
import { routeChange } from './state/actions-creators';
import { store } from './state/storeSetting';
const history = createBrowserHistory();

history.listen(({ action, location }) => {
  store.dispatch(routeChange({ pathname: location.pathname }));
});

export default history;

 

 history.js

 

 위처럼 history.js 파일을 만들어서 싱글톤으로 export를 해주고, router 등록할 때 history 객체를 주입시켜주면 history의 변경 이벤트 ( url변경) 가 발생할때마다 이벤트를 받아 볼 수가있다.

 

 

GitHub - remix-run/history: Manage session history with JavaScript

Manage session history with JavaScript. Contribute to remix-run/history development by creating an account on GitHub.

github.com

 history api 의 설명부분의 listen 부분

 

 

 또한 redux의 state에 액션을 디스패치할때도 react element 밖에서 액션을 디스패치 할 수있는데 이를 통해서

이전 상태의 정보를 관리할 수있게된다. 

 물론 react-router을 쓰게되면 context api 처럼 history의 상태를 알아서 관리를 해주지만 우리는 좀더 history정보를 커스텀하게 꺼내와서 관리를 하고 싶어서 이런 구성을 하는것이다.

 

 이벤트를 통한 redux에 pathname 을 저장해 , 이전 패스와 비교하기


import { ROUTE_CHANGE } from '../action-types';

export const routeChange =
  ({ pathname }) =>
  async dispatch => {
    // console.log(pathname);
    dispatch({ type: ROUTE_CHANGE, payload: pathname });
  };

state/action-creators/routeChange.js

 

간단하다 위 history.js 에서 listen이벤트가 발생하면 ROUTE_CHANGE 라는 액션을 발생시키는 코드이다. pathname을 인자로 받는다.


import { ROUTE_CHANGE } from '../action-types';
const INITIAL_STATE = {
  currentPage: '',
  slideFromDirection: ''
};

const pageOrder = [
  // '/', // 메인 페이지
  '/ticketing/landing', // 티켓예매 처음일때 ( 인증안되었을 때 들어가는 페이지)
  '/list/landing', // 리스트 랜딩용 페이지
  '/auth/message', // 인증용 메세지 보내는 페이지
  '/auth/validation', // 인증 시키는 페이지
  '/ticketing/amount', // 티켓 수량 입력
  '/ticketing/deposit', // 티켓 입금자명, 입금주소 모달창 띄우는 페이지
  '/list/mytickets', // 내 티켓 리스트, 패스이름 바꾸고 싶음 바꿔도됨, 밑에 processForValidationNextPage 도 바꿔주삼 - 2월 1일 11:52 이찬진
];

// eslint-disable-next-line import/no-anonymous-default-export
export default function (state = INITIAL_STATE, action) {
  switch (action.type) {
    case ROUTE_CHANGE:
      let pathname = action.payload;

      // 슬라이드 방향 디력선 설정

      let slideFromDirection = 'right';
      if (pageOrder.indexOf(state.currentPage) > pageOrder.indexOf(pathname)) {
        slideFromDirection = 'left';
      }
      // 기본
      return {
        ...state,
        currentPage: pathname,
        slideFromDirection: slideFromDirection,
        location: action.payload.location
      };

    default:
      return state;
  }
}

state/reducers/routePagination.js

 이 코드말고도 , 티켓예매냐, 내티켓확인 이냐 에따른 프로세스에 관한 다음에 보여줄 화면을 결정하는 코드도 있지만 그부분은 제외시키고 슬라이드 방향에 관한 설정 코드를 기술하였다.  간단하다 순서를 정하고싶은 pageOrder 를 리스트로 선언해서

해당 pathname과 이전 state에서 가지고있었던 pathname 정보를 비교를 해서 슬라이드 방향이 왼쪽이냐 오른쪽이냐를 결정을 한다.

 

슬라이드 방향을 결정한후 , 리덕스에서 슬라이드 방향정보에따른 애니메이션 방향 결정하기


 

지금까지 history 객체를 커스텀하여설정해서 redux에서 페이지에 관한 정보를 등록함과동시에 , 순서에따른 slideFromDirection 정보를 저장해서 , 좌우 슬라이딩 방향을 결정 하였다. 이제 그려주기만 하면된다.

import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { Routes, Route, useLocation } from 'react-router-dom';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import MessageValidationProcess from '../MessageValidationProcess/MessageValidationProcess';
import TicketingProcess from '../TicketingProcess/TicketingProcess';
import './Pagination.css';
import ListProcess from '../ListProcess/ListProcess';
import TicketCodePage from '../ListProcess/TicketCodePage/TicketCodePage';

function Pagination() {
  const location = useLocation();
  const slideFromDirection = useSelector(
    state => state.routePagination.slideFromDirection
  );
  
  return (
    <TransitionGroup
      className="transitions-wrapper"
      childFactory={child => {
        return React.cloneElement(child, {
          classNames: slideFromDirection
        });
      }}
    >
      <CSSTransition
        key={location.pathname}
        classNames={slideFromDirection}
        timeout={1000}
      >
        <Routes location={location}>
          <Route
            path="/ticketing/*"
            element={<TicketingProcess location={location} />}
          />
          <Route path="/list/*" element={<ListProcess location={location} />} />
          <Route
            path="/auth/*"
            element={<MessageValidationProcess location={location} />}
          />
        </Routes>
      </CSSTransition>
    </TransitionGroup>
  );
}

export default Pagination;

 components/Pagination/Pagination.js

 고스락 프로젝트에서 메인페이지를 제외한 애니메이션이 필요한 부분들을 Process 컴포넌트 형태로 제공하는데 , TicketingProcess 파일을가면 또 Routes가 존재한다. 일정부분을 잘 감싸서 애니메이션을 제공하는 context api 같은 형식이라고 생각하시면 된다.

 

useSelector를 활용해서 redux에 저장되었던 슬라이딩 정보를 꺼내와준후에, CSSTransiton 컴포넌트에 classNames를 지정해준다.

.transitions-wrapper {
  position: relative;
  overflow: hidden;
  height: calc(var(--vh, 1vh) * 100);
  width: 100vw;
}
.right-enter {
  /* z-index: 0; */
  transform: translateX(100%);
}

.right-enter-active {
  /* z-index: 1; */
  transform: translateX(0);
  transition: transform 300ms ease-in-out;
  /* transition-timing-function: ease-in-out; */
}

.right-exit {
  /* z-index: 1; */
  transform: translateX(0);
}

.right-exit-active {
  /* z-index: 0; */
  transform: translateX(-100%);
  transition: transform 300ms ease-in-out;
  /* transition-timing-function: ease-in-out; */
}

.left-enter {
  /* z-index: 0; */
  transform: translateX(-100%);
}

.left-enter-active {
  /* z-index: 1; */
  transform: translateX(0);
  transition: transform 300ms ease-in-out;
  /* transition-timing-function: ease-in-out; */
}

.left-exit {
  /* z-index: 1; */
  transform: translateX(0);
}

.left-exit-active {
  /* z-index: 0; */
  transform: translateX(100%);
  transition: transform 300ms ease-in-out;
  /* transition-timing-function: ease-in-out; */
}

components/Pagination/Pagination.css

slideFromDirection 정보에는 left , right 두가지 정보가 들어가있고 위 css 파일과같이 왼쪽으로 넘어오는거면 

enter , enter-active , exit , exit-active 앞에 left를 쓸지 right를 쓸지 결정해준다고 보면된다.

주의할점은

 <TransitionGroup
      className="transitions-wrapper"
      childFactory={child => {
        return React.cloneElement(child, {
          classNames: slideFromDirection
        });
      }}
    >

TransitonGroup의 childeFactory 부분인데, 이부분이 없으면 slideFromDirection 정보가 바뀐뒤에도 계속 방향이 일정하게 남아있게된다. 그러면초기 한방향으로만 고정되어 움직이게된다. 따라서 위부분을 꼭추가해서 슬라이드 방향정보가 바뀌면 슬라이드 방향도 바뀔수 있게 세팅하도록하자.

별첨 : history.state를 이용해 좀더 쉽게 애니메이션 넣는 방법


본인의 프로젝트에서 애니메이션이 들어갈부분이 얼마 없다면, 지금까지 기술한 redux에 정보를 저장해서 공통으로 관리하는 느낌이아닌

history.state를 이용해서 앞으로 갈지 뒤로갈지 방향정보를 줄 수가 있다.

 어떤 한 컴포넌트에서 버튼을 눌렀을때 ( history 는 지금 전역객체로 커스텀해서 가져온거다. 만약 커스텀하지 않으셨다면

react-router-dom v6 기준 useNavigate를 쓰시면된다. history를 커스텀하셔도 밑에 navigate 형식으로 하셔도 동작한다.)

history.push(
      '/tickets/check',
      { slideDirectionFrom : 'right' }
);

//or
const navigate = useNavigate();
navigate('/tickets/check', {
       state: { slideDirectionFrom : 'right' }
 });

 위와같은 방식으로 state를 넘기게되면, 해당 라우터에 속해있는 컴포넌트들에서 

import { useLocation } from 'react-router-dom';
import history from '../../../history';

//컴포넌트안
const location = useLocation();
useEffect(() => {
    // 커스텀 히스토리를 이용했을 때
    console.log('history.location.state:', history.location.state); 
    //result: '{ slideDirectionFrom : "right" }'
    // 기본제공 로케이션을 이용했을 때
    console.log('location:', location);
    //result: '{pathname: '/tickets/check', search: '', hash: '', state: {…}, key: 'xf82gqmb'}'
  }, [location]);

이런식으로 받아볼수있다. 그럼뭐 간단하게 리덕스를 이용안해도 방향을 쉽게 결정할수 있을것이다.

 


 위 프로젝트는 고스락 티켓예매 프로젝트 21th 에 관련한 내용입니다.

소스 전부는 아래에서 보실수 있습니다!

 

GitHub - Gosrock/Ticket-Front-21th: 고스락 티켓 프론트 레포

고스락 티켓 프론트 레포. Contribute to Gosrock/Ticket-Front-21th development by creating an account on GitHub.

github.com

 

오랜만에 블로그...복귀를 했다

디프만 11기로 활동하고 , 티키타가 앱만들고 , 고스락 22th 프로젝트 현재진행중이다.! 

좀더 열심히 써야지...!

 

‎티키타카(TikiTaka)

‎실시간 궁금한 장소에 대해 대화를 나누고 싶다면, 티카타카! 지금 대학 근처 카페에 공부할 자리가 있을까? 한강 공원에 바람이 많이 불고 있나? 티키타카 서비스를 통해 실시간으로 궁금한

apps.apple.com