본문 바로가기
프론트엔드

Next.js + Mobx 로 Toast alert 만들어보기

반응형

안녕하세요. 메리입니다. 

채용공고를 보던 중 상태관리 Mobx에 대한 내용이 종종 보이더라고요.

그래서 "농실농실" 서비스 개선 할 겸 Mobx를 사용해 Toast alert를 만들어 보았습니다. 

 

아참! 그리고 디스콰이엇에 농실농실 관련 포스팅이 올라갔습니다. 많은 관심 부탁드려요~🎉

 

농산물 실시간 경매 가격 조회앱 출시기 - 농실농실 | Disquiet*

안녕하세요. 스터디 그룹 코드드림을 운영하고 있는 이승현입니다.최근 개발된 앱들이 있어 디스콰이엇에 소개도 할겸 프로덕트를 올려봤는데, 메이커로그 제안을 받아 이렇게 글을 작성하게

disquiet.io


1. Mobx

Mobx는 저희가 흔히 알고 있는 Redux와 또 다른 상태 관리 라이브러리입니다. 

 

객체지향 느낌의 라이브러리로 보일러플레이트 코드로 Component와 State를 연결하는 방식과 달리

데코레이터(애노테이션)를 통해 해결하기 때문에 구조가 간단하다는 장점이 있습니다. 

 

npm trends에서는 Redux가 1등, Apollo Client가 2등, Mobx가 3등인 것을 확인하실 수 있습니다. 

1-1. Observable

Mobx가 동작하는 가장 기본 개념은 아래와 같습니다. 

@observable 데코레이터로 지정한 State는 관찰대상으로 지정되고 State는 값이 변경될 때마다 Rerendering 됩니다. 

  • Observable State : 관찰받고 있는 상태
  • Computed Value : 연산된 값
  • Reactions : 반응 (Computed Value가 바뀜에 따라 해야 하는 일)
  • Actions : 행동 (Observable State가 바뀜에 따라 해야 하는 액션)

 


2. Mobx 사용해 보기 

설치

mobxmobx-react를 설치합니다. mobx-react는 v6부터 hooks 문법을 지원합니다.

npm i mobx mobx-react

 

저는 react-hook-form을 사용해 필수값 체크 alert를 toast로 제작하고 싶었습니다. 

고려해야 할 사항은 아래와 같았습니다. 

  • 필수값 alert가 여러 개 뜰 수 있어야 함
  • 위에서 아래로 보이는 애니메이션이 있어야 함
  • 일정시간이 지나면 사라져야 함

Store

스토어를 만들기 위해 app > store > store.tsx 파일을 생성해 줍니다. 

import { observable } from "mobx";

export type ToastData = {
  id: number;
  content: string;
};

interface Toast {
  toastList: ToastData[];
  currentId: number;
  addToast: (content: string) => void;
  removeToast: (id: number) => void;
}

export const Toast = observable<Toast>({
  toastList: [],
  currentId: 0,

  removeToast(id) {
    const index = this.toastList.findIndex((v) => v.id === id);

    if (id !== -1) {
      this.toastList.splice(index, 1);
    }
  },

  addToast(content) {
    this.toastList.push({ id: this.currentId, content });
    this.currentId++;
    
    //일정 시간이 지나면 삭제
    setTimeout(() => {
      Toast.removeToast(this.currentId);
    }, 4000 + Toast.currentId * 500);
  },
});

 

Mobx에서 스토어를 만들기 위해서는 다음과 같이 Toast라는 객체를 선언한 후 observable로 감싸주어야 합니다. 

 

observable이 상태가 변화하는지 관찰해 줍니다. 

addToast나 removeToast 같은 액션들도 스토어 안쪽에서 같이 작성해서 사용하도록 합니다. 

 

hooks

컴포넌트마다 스토어를 사용하기 위해 useStore 컴포넌트를 아래와 같이 작성해 줍니다. 

import { Toast } from "./stores/store";

const useToast = () => ({ Toast });

export default useToast;

 

3. Toast alert 만들기

Toast alert들을 보여주는 컴포넌트를 하나 만들었습니다. 

app > component > Toast.tsx

import { CircleAlert } from "lucide-react";
import { useObserver } from "mobx-react";
import styled, { keyframes } from "styled-components";

import useToast from "@hook/useToast";

const ToastCard = styled.li<{ index: number }>`
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 8px;
  box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
  position: relative;
  min-width: 260px;
  min-height: 46px;
  background-color: rgb(255, 255, 255);
  font-size: 1.4rem;
  font-weight: 500;
  animation: slide-bottom ${(props) => `${3.5 + props.index * 0.5}s`}
    cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
  animation-delay: ${(props) => `${props.index * 0.5}s`};
  border-radius: 16px;

  svg {
    width: 24px;
    height: 24px;
    stroke: var(--primary-color);
  }

  @keyframes slide-bottom {
    0% {
      transform: translateY(${(props) => `${(props.index + 1) * -200}%`});
    }
    10% {
      transform: translateY(0);
    }
    90% {
      transform: translateY(0);
    }
    100% {
      transform: translateY(${(props) => `${(props.index + 1) * -200}%`});
      display: none;
    }
  }
`;

const ToastItem = () => {
  const {
    Toast: { toastList },
  } = useToast();

  //toastList를 가져와야 하기에 useObserver로 감싸줍니다.
  return useObserver(() => (
    <ul className="toast_wrap">
      {toastList.map((ele, index) => {
        return (
          <ToastCard key={ele.id} index={index}>
            <CircleAlert />
            {ele.content}
          </ToastCard>
        );
      })}
    </ul>
  ));
};

export default ToastItem;
//toastList를 가져와야 하기에 useObserver로 감싸줍니다.

 

toastList는 스토어에서 데이터를 가져와야 그려줄 수 있기에  해당 컴포넌트를 useObserver로 감 싸워야 합니다. 

또한 style-components의 props 사용해 동적으로 애니메이션 변화를 주었습니다. 

 

애니메이션 부연설명은 여기에📌

더보기

저의 경우 4초 후 alert를 없애고자 아래와 같이 사용했습니다. 

 

- 애니메이션 

  animation: slide-bottom ${(props) => `${3.5 + props.index * 0.5} s`} cubic-bezier(0.25, 0.46, 0.45, 0.94) both;

  animation-delay: ${(props) => `${props.index * 0.5} s`};

 

toast 스토어에서는 4초 후 해당 toast를 제거해 줍니다. 

때문에 애니메이션의 기본 유지시간을 3.5초로 잡았습니다. 

 

또한, toast 알림이 여러 개 나올 수 있기 때문에 delay 시간을 추가로 부여했습니다. 

(ex, 첫 번째 toast 0초 -> 두 번째 toast 0.5초 -> 세 번째 toast 알림 1초)

 

delay 시간을 주었기 때문에 animation-duration도 변경이 필요했습니다.  

(ex, 첫 번째 toast 3.5초 -> 두 번째 toast 4초 -> 세 번째 toast 4.5초)

 

app > search > search.tsx

toast alert를 보여주기 위해서 react-hook-form이 onInvalid 될 때 error 객체를 toast 스토어에 추가해 주었습니다. 

※ 해당 부분은 코드를 일부만 공개하도록 하겠습니다. 

  const onInvalid = async (errors: { [key: string]: any }) => {
    Object.entries(errors).forEach(([key, error]) => {
      Toast.addToast(error?.message);
    });
  };

 

이런 식으로 반복문을 돌면서 Toast alert에서 보일 메시지를 저장해 주었습니다. 


3. 확인


 

이렇게 간단하게 Mobx를 사용해서 Toast alert를 만들어보았습니다. 

전 사실, 상태관리 라이브러리라곤 Redux 밖에 몰랐는데,

Mobx라는 새로운 라이브러리를 사용해 볼 수 있어서 재미있었습니다. 

 

그저 충격스러운것은 오늘이 아직 수요일 밖에 안됬다는 것.

 

저희는 스터디를 통해 글을 기록하고 있습니다. 피드백은 언제나 환영입니다 :)

반응형