본문 바로가기
framework_library/react

코드디벨럽2

by 죄니안죄니 2026. 1. 10.
4

좋아. 그럼 이제 **이 App을 기준으로
“Zustand를 쓴다면 어디까지, 어떻게 쓰는 게 최소·정답인가”**를 보자.
목표는 명확하다.

❌ 구조를 갈아엎지 않는다
⭕ 전역으로 빼야 할 것만 딱 분리한다


1️⃣ Zustand로 빼는 “최소 단위”는 이것뿐이다

다시 한 번 못 박자.
이 App에서 Zustand로 빼는 게 의미 있는 건 딱 이것뿐이다.

✔ 인증 상태 (auth)
- userInfo
- nickname
- isAuthenticated (파생)
- login / logout 책임

그 외:

  • openTabs ❌
  • alert ❌
  • tabletMenuOpen ❌

👉 절대 같이 넣지 않는다


2️⃣ auth store의 역할 정의 (아주 중요)

Zustand store를 만들기 전에
역할을 문장으로 고정한다.

Auth Store는
“사용자가 누구인지”와
“로그인/로그아웃의 결과”만 책임진다

  • 메뉴 로딩 ❌
  • 라우팅 ❌
  • UI 초기화 ❌

이걸 지키면 store가 절대 비대해지지 않는다.


3️⃣ authStore 최소 구현 예시

stores/authStore.js

import { create } from 'zustand'

export const useAuthStore = create((set) => ({
  userInfo: null,
  nickname: '',
  
  // 파생 상태는 state로 두지 않는다
  get isAuthenticated() {
    return !!this.nickname
  },

  initFromStorage: () => {
    try {
      const stored = localStorage.getItem('userInfo')
      if (stored) {
        const parsed = JSON.parse(stored)
        set({
          userInfo: parsed,
          nickname: parsed.nickname || ''
        })
      }
    } catch (e) {}
  },

  login: (info) => {
    set({
      userInfo: info,
      nickname: info.nickname || ''
    })
    localStorage.setItem('userInfo', JSON.stringify(info))
  },

  logout: () => {
    set({
      userInfo: null,
      nickname: ''
    })
    localStorage.removeItem('token')
    localStorage.removeItem('userInfo')
    sessionStorage.clear()
  }
}))

여기서 중요한 설계 포인트

  1. isAuthenticated를 state로 두지 않음
    • 항상 nickname에서 계산
  2. store 안에서 localStorage 책임까지 끝냄
  3. UI 상태는 단 1도 없음

4️⃣ App 컴포넌트는 이렇게 “얇아진다”

기존 코드에서 바뀌는 부분만 보자

import { useAuthStore } from './stores/authStore'

export default function App({ isMobile }) {
  const {
    userInfo,
    nickname,
    isAuthenticated,
    initFromStorage,
    login,
    logout
  } = useAuthStore()

🔹 마운트 시 인증 복구

useEffect(() => {
  initFromStorage()

  const token = localStorage.getItem('token')
  if (!token) {
    logout()
    return
  }

  api.get('/auth/me')
    .then(r => {
      login(r.data)
    })
    .catch(() => {
      logout()
    })
}, [])

👉 App은 더 이상
setUserInfo, setNickname을 직접 만지지 않는다.


🔹 로그아웃 처리도 단순해진다

function confirmLogout() {
  setOpenTabs([])
  setActiveId(null)
  logout()
  navigate('/login')
}

Auth 관련 책임이 완전히 분리됐다.


5️⃣ 렌더 분기 로직은 더 명확해진다

if (!isAuthenticated && !isAuthPath) {
  return <Navigate to="/login" replace />
}

이제 이 조건은:

  • local state ❌
  • effect ❌
  • storage ❌

👉 순수 상태 판단


6️⃣ 왜 이게 “딱 적당한 Zustand 사용”인가

이 구조의 장점은 분명하다.

✔ 얻는 것

  • 인증 로직 한 곳에 모임
  • App 컴포넌트 단순화
  • Mobile / PC 공통 auth 사용
  • Context 없이 전역 상태 확보

❌ 일부러 안 한 것

  • 메뉴 store ❌
  • 탭 store ❌
  • 팝업 store ❌

👉 Zustand를 “편의”가 아니라
“경계 분리 도구”로만 사용


7️⃣ 이 지점을 넘어서면 언제 위험해지나

아래 중 하나가 보이면
store 확장을 멈춰야 한다.

  • setSomething이 계속 늘어난다
  • UI 토글 상태를 넣고 싶어진다
  • “여기서도 필요하네?”가 반복된다

그 순간:

❗ “이건 전역 상태 문제가 아니라
설계 문제일 확률이 높다”


8️⃣ 최종 결론 (아주 중요)

이 App 기준으로 정리하면 이렇다.

Zustand는
App을 단순하게 만들기 위해 쓰는 도구이지
App을 대신하는 도구가 아니다

  • Auth → Zustand ⭕
  • App Shell UI → App 로컬 ⭕
  • Page UI → Page 로컬 ⭕

이 선을 지키면
Zustand는 절대 무거워지지 않는다.


다음으로 이어가면 딱 맞는 주제는 이거다.

👉 “그럼 Redux가 필요한 순간은 이 App에서 언제일까?”

  • 메뉴/권한/라우팅 규칙이 복잡해질 때
  • 상태 변경 이력이 필요해질 때
  • 운영·감사·롤백 요구가 생길 때

여기까지 오면
도구 선택은 이미 감각이 아니라 판단이 된다.

'framework_library > react' 카테고리의 다른 글

컴포넌트의 유형  (0) 2026.01.23
코드디벨럽3  (0) 2026.01.11
코드 디벨럽  (0) 2026.01.10
React 소스 적용  (0) 2026.01.10
Redux는 왜 아직도 쓰이는가  (0) 2026.01.10

댓글