


좋다.
이 소스는 지금까지 우리가 정리한 React 사고방식이 거의 그대로 구현된 예제다.
아래에서는 **“이 코드가 실제로 어떻게 살아 움직이는지”**를
렌더링 → 상태 → 라우팅 → 전역/로컬 책임 관점에서 차근차근 해부한다.
0️⃣ 이 App 컴포넌트의 정체부터
먼저 한 문장으로 정의하자.
이 App 컴포넌트는
“인증 상태를 기준으로
전체 앱의 레이아웃과 라우팅을 결정하는 최상위 오케스트레이터”다
- UI를 많이 그리지 않는다
- 대신 결정만 한다
- 로그인인가?
- 모바일인가?
- PC인가?
- 공용 화면인가?
- 보호된 화면인가?
👉 전형적인 Root / Shell 컴포넌트 역할
1️⃣ 최초 진입 시: 렌더링이 어떻게 시작되는가
① App 함수 실행 (렌더링)
export default function App({ isMobile }) { ... }
React는 여기서 그냥 함수 실행을 한다.
아직 DOM ❌, 화면 ❌
이 시점에 실행되는 것:
- useState 초기화
- useNavigate() 호출
- JSX return 분기 계산
2️⃣ 초기 상태 설계 (아주 중요)
🔹 인증 상태의 “진실 원본”
const [userInfo, setUserInfo] = useState(() => {
const stored = localStorage.getItem('userInfo');
return stored ? JSON.parse(stored) : null;
});
const [nickname, setNickname] = useState(() => {
...
});
여기서 설계가 아주 좋다.
- 서버가 아니라
- localStorage를 1차 진실로 사용
이 의미는 이거다.
“앱 새로고침 시에도
최대한 빠르게 UI를 복구하겠다”
👉 UX 최적화
👉 그리고 나중에 서버로 검증
3️⃣ 인증 여부는 단 하나의 파생 상태로 결정됨
const isAuthenticated = !!nickname;
아주 중요한 설계다.
- userInfo ❌
- token ❌
- roles ❌
👉 딱 하나로 통일
이 덕분에:
- 조건문이 단순해지고
- 렌더 분기가 명확해진다
4️⃣ 첫 번째 useEffect — 메뉴 로딩
useEffect(() => {
if (isAuthenticated) {
api.get('/menus') ...
} else {
setMenus([]);
}
}, [isAuthenticated, isMobile]);
이 effect의 의미
“인증 상태 + 디바이스 타입이 바뀌면
메뉴는 다시 계산되어야 한다”
이건 완벽한 useEffect 사용 예다.
- 외부 세계(API)
- 렌더 결과 아님
- 의존성 명확
👉 우리가 앞에서 말한
“useEffect는 React 바깥과 연결할 때만 쓴다”
그 정석 그대로다.
5️⃣ 두 번째 useEffect — 인증 검증 (핵심)
useEffect(() => {
const token = localStorage.getItem('token')
if (!token) {
setNickname('')
setUserInfo(null)
return
}
// 1. localStorage 즉시 반영
// 2. 서버로 재검증
}, [])
이 흐름이 아주 중요하다.
순서 정리
- 토큰 없음
- 즉시 로그아웃 상태로 전환
- 토큰 있음
- localStorage 정보로 UI 즉시 렌더
- /auth/me 호출
- 성공 → 최신 정보로 갱신
- 실패 → confirmLogout()
👉 낙관적 렌더링 + 비관적 검증
실무에서 가장 안정적인 패턴이다.
6️⃣ 이제 렌더 분기 로직이 시작된다 (진짜 핵심)
여기부터가 이 App의 “두뇌”다.
🔹 1단계: / 루트 접근
if (window.location.pathname === '/') {
return <Navigate to={isAuthenticated ? "/dashboard" : "/login"} />
}
👉 앱의 단일 진입 규칙
🔹 2단계: 로그인한 사람이 로그인 페이지에 있으면?
if (isAuthenticated && isAuthPath) {
return <Navigate to="/dashboard" />
}
👉 의미 없는 화면 차단
🔹 3단계: 비로그인 상태에서 보호된 경로 접근
if (!isAuthenticated && !isAuthPath) {
return <Navigate to="/login" />
}
👉 라우터 가드 역할
7️⃣ Public Layout vs Private Layout 분리
🔹 비인증 상태
if (!isAuthenticated) {
return (
<div className="app-public">
<Routes>...</Routes>
</div>
);
}
특징:
- 메뉴 ❌
- 탭 ❌
- 헤더 ❌
- 로그인 / 회원가입만 존재
👉 완전 분리된 레이아웃
8️⃣ 인증 후: 모바일 vs PC 분기
if (isMobile)
return <MobileApp ... />
이게 중요한 이유:
PC / Mobile은
상태는 공유하지만
레이아웃 책임은 완전히 다르다
👉 아주 좋은 분리다.
9️⃣ PC Private Layout의 상태 책임 분해
여기서 지금까지 배운 게 다 나온다.
🔹 App이 소유한 상태들
menus // 앱 전체 네비게이션
openTabs // 앱 전역 탭 상태
activeId
alert
detail
showLogoutConfirm
이 상태들의 공통점:
- 여러 하위 컴포넌트가 공유
- 페이지 전환과 무관
- 앱 셸의 일부
👉 App이 주인인 게 맞다
🔟 LeftMenu / TopTabs / Content 관계
LeftMenu ── openMenu ─┐
├─ openTabs / activeId
TopTabs ── onSelect ─┘
👉 상태는 App
👉 행동은 자식
👉 결정은 App
이 구조가 깨끗한 이유:
- 자식은 상태를 만들지 않는다
- 자식은 “이벤트만 전달”
- App이 유일한 조정자
1️⃣1️⃣ logout 흐름은 완벽한 상태 수명 관리 예
function confirmLogout() {
setOpenTabs([])
setActiveId(null)
localStorage.clear()
setNickname('')
setUserInfo(null)
navigate('/login')
}
이게 중요한 이유:
- UI 상태 초기화
- 인증 상태 제거
- 라우팅 이동
👉 상태 생명주기를 한 곳에서 종료
우리가 말한:
“상태는 책임이 끝나는 곳에 둔다”
그 문장의 실전 구현이다.
1️⃣2️⃣ 전체를 한 문장으로 요약하면
이 App은 이렇게 동작한다.
App은
인증 상태를 기준으로
“어떤 레이아웃을 쓸지”를 결정하고,
상태의 주인을 명확히 한 채
자식 컴포넌트는 이벤트만 위임한다
🔚 마지막 정리 (중요)
이 코드가 좋은 이유는:
- 전역 상태 라이브러리 ❌
- Context 남용 ❌
- useEffect 남발 ❌
대신:
- 상태 위치 명확
- 책임 분리 명확
- 렌더링 흐름 예측 가능
👉 지금까지 우리가 이론으로 정리한 React 사고방식이
실제 코드로 정확히 구현된 상태다
'framework_library > react' 카테고리의 다른 글
| 코드디벨럽2 (0) | 2026.01.10 |
|---|---|
| 코드 디벨럽 (0) | 2026.01.10 |
| Redux는 왜 아직도 쓰이는가 (0) | 2026.01.10 |
| 그럼 Zustand는 왜 덜 위험한가 (0) | 2026.01.10 |
| Context는 왜 상태 관리 도구가 아닌가 (0) | 2026.01.10 |
댓글