대규모 프로젝트를 위한 Redux vs Recoil 기술 비교

odada·2025년 1월 6일
0

next.js

목록 보기
8/12

1. 개요

대규모 React 프로젝트에서 상태 관리는 애플리케이션의 성능과 유지보수성에 직접적인 영향을 미칩니다. Redux와 Recoil은 각각 다른 접근 방식을 가진 인기 있는 상태 관리 라이브러리입니다.

2. 기술 스택 비교

Redux

  • 2015년 출시, 성숙한 생태계
  • 예측 가능한 단방향 데이터 흐름
  • DevTools를 통한 강력한 디버깅
  • Middleware 시스템을 통한 확장성

Recoil

  • 2020년 Facebook에서 출시
  • React 전용으로 설계
  • 비동기 데이터 처리에 최적화
  • 간단한 API와 낮은 러닝커브

3. 코드 구현 비교

3.1 기본 상태 관리

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} />;
}

3.2 비동기 데이터 처리

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>
  );
}

4. 대규모 프로젝트에서의 장단점

Redux 장점

  1. 예측 가능한 상태 관리

    • 중앙집중식 스토어
    • 명확한 데이터 흐름
    • 강력한 미들웨어 시스템
  2. 뛰어난 개발자 도구

    • 상태 변화 추적
    • Time-travel 디버깅
    • 액션 로깅
  3. 검증된 확장성

    • 대규모 애플리케이션에서 검증됨
    • 풍부한 미들웨어 생태계
    • 체계적인 비동기 처리

Recoil 장점

  1. React 친화적 설계

    • React Suspense 통합
    • 간단한 비동기 처리
    • 코드 분할 용이
  2. 유연한 상태 관리

    • 세분화된 상태 업데이트
    • 상태 간 의존성 관리
    • 동시성 모드 지원
  3. 낮은 진입 장벽

    • 직관적인 API
    • React 스러운 사용법
    • 적은 보일러플레이트

5. 선택 가이드

Redux 선택이 좋은 경우

  • 큰 규모의 팀과 프로젝트
  • 복잡한 상태 로직이 많은 경우
  • 엄격한 상태 관리가 필요한 경우
  • 레거시 시스템과의 통합이 필요한 경우
// 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 선택이 좋은 경우

  • React 전용 프로젝트
  • 빠른 개발이 필요한 경우
  • 비동기 데이터 처리가 많은 경우
  • 최신 React 기능을 적극 활용하는 경우
// 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;
  }
});

6. 결론

대규모 프로젝트에서는 Redux를 추천합니다:

  1. 안정성

    • 오랜 기간 검증된 아키텍처
    • 예측 가능한 상태 관리
    • 강력한 개발자 도구
  2. 확장성

    • 미들웨어를 통한 기능 확장
    • 대규모 상태 관리에 적합
    • 체계적인 코드 구조
  3. 유지보수성

    • 명확한 데이터 흐름
    • standardized patterns
    • 풍부한 문서와 커뮤니티

단, 새로운 React 기능을 최대한 활용하고자 하거나, 비동기 처리가 주를 이루는 프로젝트의 경우 Recoil도 고려해볼 만한 선택지가 될 수 있습니다.

0개의 댓글