zustand로 전역상태관리 (React-Typescript)

Devinix·2023년 8월 5일
0
post-thumbnail

Side컴포넌트에서 tab 메뉴를 만들었다. 나는 그 탭을 클릭하여 선택할 것이고, 선택된 tab의 id를 이용해서 컴포넌트를 조건부 렌더링 하려고 한다. 이때 조건부 렌더링이 Side 컴포넌트의 내부에서 일어난다면 useState 훅을 이용해서 간단하게 구현할 수 있다. 하지만 Side 컴포넌트 내부에 있는 tab을 클릭해, Main 컴포넌트에서 조건부 렌더링을 해야하는 상황(서로 다른 컴포넌트 간 state 공유)이라면 상태를 전역으로 관리해주어야 한다. zustand라는 라이브러리를 이용하면 이를 매우 간단하게 해결해줄 수 있다.


초기 코드를 보자


// Side.tsx

import styles from "./Side.module.scss";
import HomeIcon from "@mui/icons-material/Home";
import ConstructionIcon from "@mui/icons-material/Construction";
import YouTubeIcon from "@mui/icons-material/YouTube";
import AttachMoneyIcon from "@mui/icons-material/AttachMoney";
import LogoutIcon from "@mui/icons-material/Logout";
import { useState } from "react";

// props 타입 
interface ISideProps {
  signOutHandler: () => void;
}

// tab 메뉴 타입 
interface ITabData {
  id: number;
  content: string;
  icon: JSX.Element;
}

// 로그 아웃을 위한 props로 내려받은 signOutHandler
function Side({ signOutHandler }: ISideProps): JSX.Element {

  // map 함수로 뿌려줄 tab의 데이터
  const tabData: ITabData[] = [
    {
      id: 1,
      content: "메인 화면",
      icon: <HomeIcon className={styles.imageIcon} />,
    },
    {
      id: 2,
      content: "상품 리스트",
      icon: <ConstructionIcon className={styles.imageIcon} />,
    },
    {
      id: 3,
      content: "유튜브 리스트",
      icon: <YouTubeIcon className={styles.imageIcon} />,
    },
    {
      id: 4,
      content: "견적 리스트",
      icon: <AttachMoneyIcon className={styles.imageIcon} />,
    },
  ];

  // useState훅을 이용한 상태 관리 (초기상태는 inedx 0번(1번 tab) 활성화)
  const [activeTab, setActiveTab] = useState(tabData[0].id);

  // tab 클릭 handler (tab의 id를 이용해 선택된 tab 활성화)
  const tabClickHandler = (tabId: number): void => {
    setActiveTab(tabId);
    
    // active 확인용 console
    console.log(`${tabId}번 탭이 active`);
  };

  return (
    <>
      <div className={styles.sideContainer}>
        <ul>
          {tabData.map((tab) => {
          
            // 선택된 tab 동적 class 변수
            const tabClass = `${styles.iconContainer} ${
              activeTab === tab.id ? styles.active : ""
            }`;
            return (
              <li key={tab.id}>
                <div
                  className={tabClass}
                  onClick={() => {
                  
                    // 클릭 시 실제 tab.id를 인자로 전달
                    tabClickHandler(tab.id);
                  }}
                >
                  <div className={styles.homeContainer}>
                    {tab.icon}
                    <p>{tab.content}</p>
                  </div>
                </div>
              </li>
            );
          })}
        </ul>
        <div
          className={styles.iconContainer}
          onClick={() => {
            
            // 로그 아웃 handler
            signOutHandler();
          }}
        >
          <div className={styles.logOutIconContainer}>
            <LogoutIcon className={styles.imageIcon} />
            <p>로그 아웃</p>
          </div>
        </div>
      </div>
    </>
  );
}

export default Side;

선택된 탭의 디자인을 제어해주는 코드를 작성했다. 현재는 useState훅을 이용해서 Side 컴포넌트 내부에서만 상태를 관리해주고 있다. 이 경우에 다른 컴포넌트에서 상태를 공유할 수 없다는 문제가 발생한다. 이를 해결해 주기 위해 터미널 창을 열어 npm i zustand 명령어로 zustand 라이브러리를 설치해 주자.


npm i zustand

이제 src 디렉토리 내부에 stores 디렉토리를 생성해주고, 그 내부에 파일을 하나 만들어줘서 전역으로 상태관리를 해줄 코드를 작성하자. 파일의 이름은 useActiveTabStore로 해주었다.


// useActiveTabStore.ts

import { create } from "zustand";

// store 타입 (안해주면 에러날 수도 있음!! 타입 정의 꼭 해주기!!)
interface IUseActiveStore {
  activeTab: number;
  setActiveTab: (tabId: number) => void;
}

const useActiveTabStore = create<IUseActiveStore>((set) => ({

  // 초기 상태 (1번 탭 active)
  activeTab: 1,
  
  // 1번 tab이 active된 초기상태에서, 다른 n번째 tab 클릭 시 해당 탭 active
  setActiveTab: (tabId: number) =>
    set((state: { activeTab: number }) => ({
      activeTab: (state.activeTab = tabId),
    })),
}));

export default useActiveTabStore;
export type { IUseActiveStore };

이제 activeTab과 setActiveTab은 어느 컴포넌트에서든 import해서 상태를 가져다 쓸 수 있다.


  // useState훅을 이용한 상태 관리
  const [activeTab, setActiveTab] = useState(tabData[0].id);

이 부분을


  // zustand store를 이용해 상태 관리
  const { activeTab, setActiveTab } = useActiveTabStore();

이렇게 바꿔주기만 하면 Side 컴포넌트 내부에서는 더 이상 수정할 부분이 없다. (import 하는 것 잊지 말자)


이제 Main 컴포넌트에서 조건부 렌더링 할 코드를 작성해주자.


import Layout from "../../components/layout/layout/Layout";
import useActiveTabStore from "../../stores/useActiveTabStore";


function Main(): JSX.Element {
  const { activeTab } = useActiveTabStore();
  return (
    <Layout>
      {activeTab === 1 && <Component1 />}
      {activeTab === 2 && <Component2 />}
      {activeTab === 3 && <Component3 />}
      {activeTab === 4 && <Component4 />}
    </Layout>
  );
}

export default Main;

컴포넌트 간 state 공유 끝!

profile
프론트엔드 개발

0개의 댓글