대규모 React 프로젝트에서 상태 관리는 애플리케이션의 성능과 유지보수성에 직접적인 영향을 미칩니다. Redux와 Recoil은 각각 다른 접근 방식을 가진 인기 있는 상태 관리 라이브러리입니다.
Redux 구현
// store/slices/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface UserState {
users: User[];
selectedUserId: string | null;
loading: boolean;
}
const initialState: UserState = {
users: [],
selectedUserId: null,
loading: false
};
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
setUsers: (state, action: PayloadAction<User[]>) => {
state.users = action.payload;
},
setSelectedUser: (state, action: PayloadAction<string>) => {
state.selectedUserId = action.payload;
},
setLoading: (state, action: PayloadAction<boolean>) => {
state.loading = action.payload;
}
}
});
// 컴포넌트에서 사용
function UserList() {
const dispatch = useDispatch();
const { users, loading } = useSelector((state: RootState) => state.user);
useEffect(() => {
dispatch(setLoading(true));
fetchUsers()
.then(users => dispatch(setUsers(users)))
.finally(() => dispatch(setLoading(false)));
}, []);
return loading ? <Spinner /> : <List users={users} />;
}
Recoil 구현
// store/atoms/userAtoms.ts
import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';
const usersState = atom<User[]>({
key: 'usersState',
default: []
});
const selectedUserIdState = atom<string | null>({
key: 'selectedUserIdState',
default: null
});
const selectedUserState = selector({
key: 'selectedUserState',
get: ({get}) => {
const users = get(usersState);
const selectedId = get(selectedUserIdState);
return users.find(user => user.id === selectedId);
}
});
// 컴포넌트에서 사용
function UserList() {
const [users, setUsers] = useRecoilState(usersState);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetchUsers()
.then(setUsers)
.finally(() => setLoading(false));
}, []);
return loading ? <Spinner /> : <List users={users} />;
}
Redux with Redux-Thunk
// store/actions/userActions.ts
const fetchUserById = createAsyncThunk(
'user/fetchById',
async (userId: string, { rejectWithValue }) => {
try {
const response = await api.get(`/users/${userId}`);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// 컴포넌트에서 사용
function UserProfile({ userId }: { userId: string }) {
const dispatch = useDispatch();
const { user, loading, error } = useSelector((state: RootState) => state.user);
useEffect(() => {
dispatch(fetchUserById(userId));
}, [userId]);
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return <ProfileView user={user} />;
}
Recoil with Suspense
// store/selectors/userSelectors.ts
const userQuery = selectorFamily({
key: 'userQuery',
get: (userId: string) => async () => {
const response = await api.get(`/users/${userId}`);
return response.data;
}
});
// 컴포넌트에서 사용
function UserProfile({ userId }: { userId: string }) {
const user = useRecoilValue(userQuery(userId));
return <ProfileView user={user} />;
}
// 상위 컴포넌트
function UserProfilePage() {
return (
<Suspense fallback={<Spinner />}>
<ErrorBoundary fallback={<ErrorMessage />}>
<UserProfile userId={userId} />
</ErrorBoundary>
</Suspense>
);
}
예측 가능한 상태 관리
뛰어난 개발자 도구
검증된 확장성
React 친화적 설계
유연한 상태 관리
낮은 진입 장벽
// Redux의 강점: 미들웨어를 통한 부가 기능 구현
const loggerMiddleware = (store) => (next) => (action) => {
console.log('이전 상태:', store.getState());
console.log('액션:', action);
const result = next(action);
console.log('다음 상태:', store.getState());
return result;
};
// API 요청 추적
const apiMiddleware = (store) => (next) => (action) => {
if (action.type.endsWith('/pending')) {
analytics.trackApiRequest(action.type);
}
return next(action);
};
// Recoil의 강점: 비동기 처리와 캐싱
const userProfileWithCache = selector({
key: 'userProfileWithCache',
get: async ({get}) => {
const userId = get(currentUserIdState);
const cached = await cacheManager.get(userId);
if (cached) return cached;
const profile = await fetchUserProfile(userId);
await cacheManager.set(userId, profile);
return profile;
}
});
대규모 프로젝트에서는 Redux를 추천합니다:
안정성
확장성
유지보수성
단, 새로운 React 기능을 최대한 활용하고자 하거나, 비동기 처리가 주를 이루는 프로젝트의 경우 Recoil도 고려해볼 만한 선택지가 될 수 있습니다.