라우트 사이 관리자 정보를 Zustand로 공유해 Props Drilling을 없앤 방법
서로 다른 라우트에서 필요한 관리자 정보를 Zustand store로 공유하고, 화면 계층의 props 전달과 세션 상태 책임을 분리한 과정을 정리합니다.
학원 관리 화면에서 조회한 관리자 정보는 여러 화면에서 반복해서 필요했다. 처음에는 상위 컴포넌트가 상태를 가지고 필요한 자식에게 속성(props)으로 내려주는 방식을 생각할 수 있지만, 관리자 목록과 회원 탈퇴 화면은 서로 다른 라우트였다. 중간 레이아웃이 실제로 사용하지 않는 데이터까지 전달하게 만들면 화면 구조와 데이터 구조가 강하게 결합된다.
이 문제를 해결하기 위해 현재 관리자 정보를 Zustand store에 보관하고 필요한 라우트가 직접 구독하는 구조를 도입했다.
서로 다른 라우트에 같은 정보가 필요했다
관리자 계정의 회원 탈퇴 기능에는 로그인한 사용자의 휴대폰 번호가 필요했다. 탈퇴를 요청한 사람이 현재 계정의 소유자인지 확인하기 위해 입력한 번호와 관리자 정보를 비교해야 했기 때문이다.
관리자 정보는 학원 선택 화면에서 이미 조회하고 있었다. 이 값을 탈퇴 화면까지 전달하려고 하면 다음과 같은 문제가 생긴다.
- 라우트 사이에 임시 state를 전달해야 한다.
- 새로고침이나 직접 URL 진입 시 전달값이 사라질 수 있다.
- 중간 컴포넌트가 사용하지 않는 관리자 정보를 계속 받아서 내려줘야 한다.
- 관리자 정보가 필요한 화면이 늘어날수록 상위 컴포넌트의 책임이 커진다.
여기서 Props Drilling은 실제로 값을 사용하지 않는 중간 컴포넌트를 거쳐 하위 컴포넌트까지 props를 연속으로 전달하는 구조를 뜻한다.
관리자 정보를 공통 store로 옮겼다
최초 구현에서는 store.ts 안에 관리자 정보와 변경 함수를 가진 작은 Zustand store를 추가했다.
아래 코드 예시는 설명을 위해 식별자와 운영 설정값을 일반화했습니다.
interface AccountState {
account: { id: string; contact: string } | null;
setAccount: (account: AccountState["account"]) => void;
resetAccount: () => void;
}
export const useAccountStore = create<AccountState>((set) => ({
account: null,
setAccount: (account) => set({ account }),
resetAccount: () => set({ account: null }),
}));
store가 맡은 책임은 세 가지로 제한했다.
- 현재 관리자 정보 보관
- 관리자 정보 갱신
- 로그아웃이나 탈퇴 시 관리자 정보 초기화
화면 전용 입력값이나 모달 상태까지 한곳에 넣지 않고, 여러 라우트가 함께 사용하는 인증 사용자 정보만 공통 상태로 만들었다.
정보를 조회한 화면에서 store를 갱신했다
학원 목록 API가 사용자 정보를 반환하면 기존 로컬 state와 함께 store에도 같은 관리자 정보를 저장했다.
const { setAccount } = useAccountStore();
setAccount({ id: response.account.id, contact: response.account.contact });
이후 다른 라우트는 학원 목록 화면이나 최상위 App 컴포넌트의 props 구조를 알 필요 없이 store에서 현재 사용자를 조회할 수 있다.
다음 그림은 관리자 정보가 화면 계층이 아니라 공통 상태를 통해 전달되는 흐름을 보여준다.
flowchart LR
A[관리자 정보 조회] --> B[공통 관리자 상태 저장]
B --> C[학원 선택 화면]
B --> D[회원 탈퇴 화면]핵심은 두 화면이 서로를 참조하지 않고 공통 상태만 각자 구독한다는 점이다.
회원 탈퇴 화면은 필요한 정보만 직접 읽었다
회원 탈퇴 라우트에서는 store의 관리자 정보와 초기화 함수만 가져왔다.
const { account, resetAccount } = useAccountStore();
const matchesContact = () => account?.contact === enteredContact;
탈퇴가 끝나면 인증 토큰을 지우는 것과 함께 resetAdminUser()를 호출했다. 전역 상태는 접근하기 편한 만큼 세션 종료 시점에 명시적으로 비워야 이전 사용자의 정보가 남지 않는다.
전역 상태로 옮길 기준을 정했다
props가 여러 단계로 전달된다고 해서 모든 값을 전역 store에 넣을 필요는 없다. 이 작업에서는 다음 조건을 기준으로 삼았다.
- 서로 다른 라우트에서 같은 값이 필요하다.
- 값의 소유권이 특정 화면이 아니라 로그인 세션에 있다.
- 화면이 바뀌어도 동일한 최신 값이 유지돼야 한다.
- 로그아웃과 같은 명확한 초기화 시점이 있다.
반대로 한 화면에서만 사용하는 검색어, 선택 행과 모달 열림 상태는 기존 로컬 state에 두는 편이 변경 범위를 이해하기 쉽다.
초기 구현의 한계도 있었다
최초 adminUserStore는 메모리 기반 store였다. 브라우저를 새로고침하면 상태가 초기화되므로 서버에서 관리자 정보를 다시 조회해야 했다. 이후 인증과 토큰 관리가 확장되면서 store는 별도 파일과 영속화 구조로 분리됐지만, 출발점은 라우트 간 공통 사용자 정보의 소유권을 화면 밖으로 옮기는 것이었다.
이 변경으로 회원 탈퇴 화면은 관리자 목록 화면의 컴포넌트 구조에 의존하지 않게 됐다. 성능 개선을 위한 작업은 아니었지만, 관리자 정보가 필요한 화면을 추가할 때 중간 레이아웃의 props 계약을 계속 수정하지 않아도 되는 기반을 만들었다.