들어가며
React 애플리케이션이 복잡해질수록 상태 관리의 어려움이 증가합니다. 전역 상태 관리 라이브러리를 도입하면 이러한 문제를 해결할 수 있지만, 기존의 React 훅 사용 패턴도 함께 변경되어야 합니다. 이 가이드는 전역 상태 관리 라이브러리 도입의 이유와 그에 따른 훅 사용 가이드의 변화를 설명합니다.
결론
전역 상태 관리 라이브러리를 도입하면 다음과 같은 이점을 얻을 수 있습니다:
- Props Drilling 해결: 깊은 컴포넌트 계층에서도 쉽게 상태 공유
- 상태 동기화: 여러 컴포넌트 간 상태 일관성 보장
- 복잡한 로직 중앙화: 상태 관련 비즈니스 로직을 한 곳에서 관리
- 성능 최적화: 선택적 구독으로 불필요한 리렌더링 방지
하지만 전역 상태 관리 라이브러리를 도입할 때는 기존의 React 훅 사용 패턴도 함께 변경되어야 합니다:
- useState: 로컬 상태만 관리, 전역 상태는 스토어에서
- useMemo: 복잡한 계산은 스토어에서, 간단한 계산은 컴포넌트에서
- useEffect: API 호출은 스토어에서, 컴포넌트 초기화는 컴포넌트에서
이러한 원칙을 따르면 유지보수하기 쉽고, 성능이 좋은 React 애플리케이션을 만들 수 있습니다.
전역 상태 관리 라이브러리를 사용하는 이유
1. Props Drilling 문제 해결
❌ 전역 상태 관리 없이
// Page Component
const ChatAgentListPage = () => {
const [userPermissions, setUserPermissions] = useState([]);
const [isResourceAdmin, setIsResourceAdmin] = useState(false);
return (
<div>
<Header
userPermissions={userPermissions}
isResourceAdmin={isResourceAdmin}
/>
<AgentList
userPermissions={userPermissions}
isResourceAdmin={isResourceAdmin}
/>
<Sidebar
userPermissions={userPermissions}
isResourceAdmin={isResourceAdmin}
/>
</div>
);
};
// List Component
const AgentList = ({ userPermissions, isResourceAdmin }) => {
return (
<div>
{agents.map(agent => (
<AgentListItem
key={agent.id}
agent={agent}
userPermissions={userPermissions}
isResourceAdmin={isResourceAdmin}
/>
))}
</div>
);
};
// ListItem Component
const AgentListItem = ({ agent, userPermissions, isResourceAdmin }) => {
return (
<div>
<AgentCard
agent={agent}
userPermissions={userPermissions}
isResourceAdmin={isResourceAdmin}
/>
</div>
);
};
✅ 전역 상태 관리 사용
// Page Component
const ChatAgentListPage = () => {
return (
<div>
<Header />
<AgentList />
<Sidebar />
</div>
);
};
// List Component
const AgentList = () => {
return (
<div>
{agents.map(agent => (
<AgentListItem key={agent.id} agent={agent} />
))}
</div>
);
};
// ListItem Component
const AgentListItem = ({ agent }) => {
const { isResourceAdmin } = usePermissionStore();
return (
<div>
<AgentCard agent={agent} />
</div>
);
};
2. 상태 동기화 문제 해결
❌ 전역 상태 관리 없이
// Header Component
const Header = () => {
const [userInfo, setUserInfo] = useState(null);
useEffect(() => {
// 사용자 정보 로드
loadUserInfo().then(setUserInfo);
}, []);
return <div>Welcome, {userInfo?.name}</div>;
};
// Sidebar Component
const Sidebar = () => {
const [userInfo, setUserInfo] = useState(null);
useEffect(() => {
// 동일한 사용자 정보를 다시 로드
loadUserInfo().then(setUserInfo);
}, []);
return <div>User: {userInfo?.name}</div>;
};
✅ 전역 상태 관리 사용
// Header Component
const Header = () => {
const { userInfo } = useUserStore();
return <div>Welcome, {userInfo?.name}</div>;
};
// Sidebar Component
const Sidebar = () => {
const { userInfo } = useUserStore();
return <div>User: {userInfo?.name}</div>;
};
// 전역 상태에서 한 번만 로드
const useUserStore = createStore((set) => ({
userInfo: null,
loadUserInfo: async () => {
const userInfo = await loadUserInfo();
set({ userInfo });
},
}));
3. 복잡한 상태 로직 중앙화
❌ 전역 상태 관리 없이
// 각 컴포넌트에서 개별적으로 권한 체크
const ChatAgentCard = () => {
const isResourceAdmin = useMemo(() => {
const userResourceRoles = JSON.parse(sessionStorage.getItem('userResourceRoles') || '[]');
return userResourceRoles.some(role =>
role.roleCode && role.roleCode.toLowerCase().includes('admin')
);
}, []);
return <div>{isResourceAdmin && <SettingsButton />}</div>;
};
const Header = () => {
const isResourceAdmin = useMemo(() => {
const userResourceRoles = JSON.parse(sessionStorage.getItem('userResourceRoles') || '[]');
return userResourceRoles.some(role =>
role.roleCode && role.roleCode.toLowerCase().includes('admin')
);
}, []);
return <div>{isResourceAdmin && <AdminMenu />}</div>;
};
✅ 전역 상태 관리 사용
// 중앙화된 권한 관리
const usePermissionStore = createStore((set) => ({
userResourceRoles: [],
isResourceAdmin: false,
setUserResourceRoles: (roles) => {
const isResourceAdmin = roles.some(role =>
role.roleCode && role.roleCode.toLowerCase().includes('admin')
);
set({ userResourceRoles: roles, isResourceAdmin });
},
}));
// 각 컴포넌트에서 간단하게 사용
const ChatAgentCard = () => {
const { isResourceAdmin } = usePermissionStore();
return <div>{isResourceAdmin && <SettingsButton />}</div>;
};
const Header = () => {
const { isResourceAdmin } = usePermissionStore();
return <div>{isResourceAdmin && <AdminMenu />}</div>;
};
전역 상태 관리 도입 시 훅 사용 가이드 변경
1. useState 사용 위치 변경
기존 가이드 (전역 상태 관리 없이)
// Page Component에서 전역 상태 관리
const Page = () => {
const [globalState, setGlobalState] = useState();
const [pageState, setPageState] = useState();
return <List globalState={globalState} pageState={pageState} />;
};
새로운 가이드 (전역 상태 관리 사용)
// Page Component: 페이지 레벨 상태만 관리
const Page = () => {
const [pageState, setPageState] = useState(); // 페이지 레벨 상태만
const { globalState, setGlobalState } = useGlobalStore(); // 전역 상태는 스토어에서
return <List pageState={pageState} />;
};
// 전역 상태는 별도 스토어에서 관리
const useGlobalStore = createStore((set) => ({
globalState: null,
setGlobalState: (state) => set({ globalState: state }),
}));
2. useMemo 사용 위치 변경
기존 가이드 (전역 상태 관리 없이)
// Page Component에서 복잡한 계산
const Page = () => {
const [data, setData] = useState([]);
const [filters, setFilters] = useState({});
const processedData = useMemo(() => {
// 복잡한 데이터 가공 로직
return processData(data, filters);
}, [data, filters]);
return <List data={processedData} />;
};
새로운 가이드 (전역 상태 관리 사용)
// Page Component: 단순한 계산만
const Page = () => {
const [pageFilters, setPageFilters] = useState({});
const { processedData, setFilters } = useDataStore();
// 페이지 필터 변경 시 전역 상태 업데이트
useEffect(() => {
setFilters(pageFilters);
}, [pageFilters, setFilters]);
return <List data={processedData} />;
};
// 전역 스토어에서 복잡한 계산 처리
const useDataStore = createStore((set, get) => ({
rawData: [],
filters: {},
processedData: [],
setData: (data) => {
const { filters } = get();
const processedData = processData(data, filters);
set({ rawData: data, processedData });
},
setFilters: (filters) => {
const { rawData } = get();
const processedData = processData(rawData, filters);
set({ filters, processedData });
},
}));
3. useEffect 사용 위치 변경
기존 가이드 (전역 상태 관리 없이)
// 각 컴포넌트에서 개별적으로 API 호출
const Page = () => {
const [data, setData] = useState([]);
useEffect(() => {
loadData().then(setData);
}, []);
return <List data={data} />;
};
새로운 가이드 (전역 상태 관리 사용)
// Page Component: 단순한 초기화만
const Page = () => {
const { data, loadData } = useDataStore();
useEffect(() => {
loadData(); // 전역 스토어의 액션 호출
}, [loadData]);
return <List data={data} />;
};
// 전역 스토어에서 API 호출 관리
const useDataStore = createStore((set) => ({
data: [],
loading: false,
error: null,
loadData: async () => {
set({ loading: true, error: null });
try {
const data = await loadData();
set({ data, loading: false });
} catch (error) {
set({ error, loading: false });
}
},
}));
전역 상태 관리 도입 시 새로운 가이드
1. 상태 분류 기준
전역 상태 (Global State)
- 사용자 정보: 로그인 상태, 권한, 프로필
- 애플리케이션 설정: 테마, 언어, 설정
- 공유 데이터: API 응답 데이터, 캐시된 데이터
- 상태 관리: 로딩 상태, 에러 상태
페이지 상태 (Page State)
- 페이지 레벨 UI 상태: 모달 열림/닫힘, 탭 선택
- 페이지별 필터: 검색어, 정렬 옵션
- 페이지별 설정: 페이지 크기, 현재 페이지
컴포넌트 상태 (Component State)
- UI 상태: hover, focus, tooltip 표시
- 애니메이션 상태: 로딩 스피너, 트랜지션
- 폼 상태: 입력값, 유효성 검사
2. 새로운 훅 사용 가이드
Page Component
const Page = () => {
// 페이지 레벨 상태만 관리
const [pageState, setPageState] = useState();
const [modalOpen, setModalOpen] = useState(false);
// 전역 상태는 스토어에서 가져오기
const { globalData, loadGlobalData } = useGlobalStore();
const { userInfo } = useUserStore();
// 페이지 초기화
useEffect(() => {
loadGlobalData();
}, [loadGlobalData]);
return (
<div>
<Header />
<List data={globalData} />
<Modal open={modalOpen} onClose={() => setModalOpen(false)} />
</div>
);
};
List Component
const List = ({ data }) => {
// 리스트 레벨 상태만 관리
const [sortOrder, setSortOrder] = useState('asc');
const [expandedItems, setExpandedItems] = useState(new Set());
// 전역 상태에서 필요한 데이터 가져오기
const { userPermissions } = usePermissionStore();
// 리스트 레벨 계산
const sortedData = useMemo(() => {
return [...data].sort((a, b) => {
return sortOrder === 'asc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
});
}, [data, sortOrder]);
return (
<div>
{sortedData.map(item => (
<ListItem
key={item.id}
item={item}
isExpanded={expandedItems.has(item.id)}
onToggle={() => toggleExpanded(item.id)}
/>
))}
</div>
);
};
ListItem Component
const ListItem = ({ item, isExpanded, onToggle }) => {
// 아이템 레벨 상태만 관리
const [isHovered, setIsHovered] = useState(false);
const [isLoading, setIsLoading] = useState(false);
// 전역 상태에서 필요한 데이터 가져오기
const { isResourceAdmin } = usePermissionStore();
const { updateItem } = useItemStore();
// 아이템 레벨 계산
const canEdit = useMemo(() => {
return isResourceAdmin && item.status === 'ACTIVE';
}, [isResourceAdmin, item.status]);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<ItemCard
item={item}
isHovered={isHovered}
canEdit={canEdit}
onEdit={() => updateItem(item.id)}
/>
</div>
);
};
Button Component
const Button = ({ onClick, children }) => {
// 버튼 레벨 상태만 관리
const [isPressed, setIsPressed] = useState(false);
const [rippleEffect, setRippleEffect] = useState(null);
// 전역 상태에서 필요한 데이터 가져오기
const { isResourceAdmin } = usePermissionStore();
// 버튼 활성화 상태 계산
const isEnabled = useMemo(() => {
return isResourceAdmin && !isPressed;
}, [isResourceAdmin, isPressed]);
return (
<button
disabled={!isEnabled}
onMouseDown={() => setIsPressed(true)}
onMouseUp={() => setIsPressed(false)}
onClick={onClick}
>
{children}
</button>
);
};
3. 전역 상태 관리 라이브러리별 특징
Zustand (현재 프로젝트에서 사용)
// 간단하고 직관적인 API
const useStore = createStore((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
// 사용
const Component = () => {
const { count, increment } = useStore();
return <button onClick={increment}>{count}</button>;
};
Redux Toolkit
// 더 구조화된 접근
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
user: userSlice.reducer,
},
});
// 사용
const Component = () => {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return <button onClick={() => dispatch(increment())}>{count}</button>;
};
Jotai
// 원자적 상태 관리
const countAtom = atom(0);
const incrementAtom = atom(null, (get, set) => {
set(countAtom, get(countAtom) + 1);
});
// 사용
const Component = () => {
const [count] = useAtom(countAtom);
const [, increment] = useAtom(incrementAtom);
return <button onClick={increment}>{count}</button>;
};
전역 상태 관리 도입 시 주의사항
1. 상태 분리 원칙
- 전역 상태: 여러 컴포넌트에서 공유되는 상태
- 로컬 상태: 특정 컴포넌트에서만 사용되는 상태
- 파생 상태: 다른 상태로부터 계산되는 상태
2. 성능 최적화
// 선택적 구독으로 불필요한 리렌더링 방지
const Component = () => {
// 전체 상태 구독 (비추천)
const state = useStore();
// 필요한 부분만 구독 (권장)
const count = useStore((state) => state.count);
const increment = useStore((state) => state.increment);
return <button onClick={increment}>{count}</button>;
};
3. 상태 구조 설계
// 도메인별로 상태 분리
const useUserStore = createStore((set) => ({
user: null,
permissions: [],
isAuthenticated: false,
// 사용자 관련 액션들
}));
const useChatStore = createStore((set) => ({
messages: [],
currentChannel: null,
// 채팅 관련 액션들
}));
const useWorkflowStore = createStore((set) => ({
workflows: [],
currentWorkflow: null,
// 워크플로우 관련 액션들
}));
'웹 개발 > 프론트엔드 ∕ React' 카테고리의 다른 글
| [React] 전역 상태 관리 vs 커스텀 훅: 언제 무엇을 사용해야 할까? (0) | 2025.10.22 |
|---|---|
| [React] 훅 사용 위치 가이드: 언제 어디서 사용해야 할까? (0) | 2025.10.22 |
| [React] rc-tree 라이브러리를 통한 treeview 공통 컴포넌트 만들기 (0) | 2023.05.09 |
| [React] 개발 참고자료 목록 (0) | 2022.10.23 |
| [React] 기본 실습 (0) | 2022.06.21 |





최근댓글