React - 상태관리 Redux

Hunjin·2025년 10월 30일

SOPT

목록 보기
3/6
post-thumbnail

여러분은 React를 사용하면서 불편함을 느껴본 적 있으신가요?

React는 훌륭한 UI 라이브러리입니다.
컴포넌트 기반으로 재사용성과 효율성을 높여주고,
useState, useEffect 같은 훅들을 통해 상태 관리도 굉장히 직관적이죠.

그런데 어느 순간, 이런 생각이 들었습니다.

“왜 얘는 다른 컴포넌트랑 상태 공유가 안 되지?”

컴포넌트 간 상태 전달, 왜 이렇게 불편할까?

React로 개발하다 보면 꼭 한 번은 겪게 되는 상황이 있습니다.
부모 -> 자식으로는 props로 쉽게 전달할 수 있지만, 형제 컴포넌트 간 상태 전달은 불가능합니다.

물론 간단한 프로젝트라면 useState 만으로도 충분합니다.
그런데 프로젝트 규모가 커지고, 컴포넌트가 많아지면?

  • 상태를 전달하려고 props를 계속 타고 내려보내야 하고
  • 필요 없는 중간 컴포넌트도 props를 받아야 하고
  • 상태 흐름이 꼬이기 시작하면서 디버깅 지옥이 시작됩니다

그래서 이러한 React의 불편함을 해결하기 위해 Redux라는 라이브러리를 사용하게 되었습니다.


그럼 Redux란 무엇인가?

Redux는 상태(State)를 효율적으로 관리하기 위한 라이브러리로,
주로 React와 함께 사용됩니다.
단일 Store를 중심으로 상태를 관리하며, 예측 가능하고 일관된 방식으로 상태를 다룰 수 있게 해주는 것이 핵심 장점입니다.

여기까지만 보면
“기존에 있던 useState, useReducer와 뭐가 그렇게 다르지?”
하고 의문이 들 수 있죠.

그래서 Redux만의 특징을 몇 가지로 정리해보겠습니다.


Redux의 주요 특징

1. 중앙 집중식 상태 관리 (Store)

모든 상태를 하나의 Store에서 관리합니다.
컴포넌트가 많아져도 상태의 출처는 항상 한 곳입니다.

2. 예측 가능한 상태 흐름

모든 상태 변경은
Action -> Dispatch -> Reducer
라는 일관된 흐름을 따릅니다.
이를 통해, 상태가 언제, 왜, 어떻게 바뀌었는지 추적이 가능합니다.

3. 전역 어디서든 접근 가능

Redux Store에 접근하기만 하면
컴포넌트 간 관계와 상관없이 어떤 컴포넌트든 상태를 꺼내 쓸 수 있습니다.
props drilling 문제 해결


그럼 이제 Redux에서 데이터가 어떤 흐름으로 흘러가는지 자세하게 알아보겠습니다.

Redux는 위 그림과 같은 흐름으로 데이터가 전달됩니다.

하지만 그림만 보고 이 흐름을 단번에 이해하기는 쉽지 않습니다.
Redux 자체가 구조가 분명하면서도 처음 접하면 꽤 복잡하게 느껴지기 때문이죠.

그래서 이번엔 간단한 예시를 통해 Redux의 데이터 흐름을 직접 설명드리려 합니다.


Redux 스토리텔링 예시

1. 훈진이가 취업을 해서 서울로 상경을 했다.

“오늘 이 동네에 이사와서 전입신고 하러 왔어요!!”

이 상황은, 컴포넌트가 어떤 상태 변경이 필요하다고 느끼는 것과 같습니다.
즉, 이사를 했으니 전입신고라는 상태 변화가 필요해진 것이죠

2. 훈진이는 ‘전입신고서’를 작성하려 했지만, 양식이 잘못돼서 직원에게 도움을 요청한다.

이 상황은 Redux에서의 Action Creator와 같습니다.
훈진이는 직접 액션 객체를 만들지 않습니다.
대신, 행정복지센터 직원(Action Creator) 이 상황에 맞는 양식(Action) 을 작성해 훈진이에게 전달합니다.

function createRegisterAction(payload) {
  return {
    type: 'REGISTER',
    payload,
  };
}

3. 훈진이는 그 양식을 가지고 행정복지센터 접수처(Store)에 제출한다.

이 단계는 Redux에서의 dispatch(action)에 해당합니다.
단순히 액션을 만드는 것만으로는 아무 일도 일어나지 않습니다.
직접 해당 양식을 접수처(Store)에 제출(dispatch) 해야 비로소 상태 변경이 시작됩니다.

dispatch(createRegisterAction({name: '훈진', address: '인천'}));

4. 접수처(Store)는 서류를 받은 뒤, 내부 직원(Reducer)에게 전달한다.

접수처는 받은 전입신고서를 내부 직원에게 넘기며 이렇게 말합니다.

“기존에 있던 정보와 이번에 받은 서류를 바탕으로, 새 정보를 만들어주세요.”

이 단계가 바로 Redux에서의 Reducer입니다.

Reducer는 기존 상태(state) 와 새로운 액션(action) 을 기반으로 변경된 상태(new state) 를 만들어냅니다.
상태 변경의 핵심 로직은 모두 이 Reducer 내부에서 처리됩니다.

function reducer(state, action) {
  switch (action.type) {
    case 'REGISTER':
      return {
        ...state,
        user: action.payload, // 이전 정보에 새로운 전입신고 내용 갱신
      };
    default:
      return state;
  }
}

5. 직원은 새롭게 갱신된 주민 정보를 만들어 다시 접수처(Store)에 전달한다.

이 단계는 Redux에서 return newState에 해당합니다.
Reducer가 만든 새로운 상태(new state) 는 Store에 저장되고,
Store는 이 변경 사항을 구독 중인 컴포넌트들에게 자동으로 알려줍니다.


6. 훈진(컴포넌트)은 바뀐 주소가 적혀있는 주민등록증을 확인한다.

Redux Store의 상태가 변경되면,
이를 구독하고 있는 컴포넌트는 자동으로 리렌더링됩니다.
결과적으로 최신 상태를 기반으로 화면이 갱신되죠.


그럼 이제 Redux를 통해 데이터가 어떤 흐름으로 흘러가는지 어느 정도 이해가 됐을까요?
다음으로는 Redux를 이용한 간단한 카운터 앱 예시를 통해 실제로 Redux를 어떻게 사용하는지 알아보겠습니다.

Store - 상태를 보관하는 창고

import { createStore } from "redux";
import counterReducer from "./counterReducer";

const store = createStore(counterReducer);

export default store;

Action 정의 – 무슨 일이 일어났는지를 설명하는 객체

export const COUNTER_ACTION = {
  UP: 'countUp',
  DOWN: 'countDown',
  RESET: 'countReset'
};

Redux에서 상태를 변경하려면 액션(Action) 객체가 필요합니다.
type 값으로 어떤 행동인지 구분하고, 필요하다면 payload로 데이터도 같이 넘깁니다.

Action Creator – 액션 객체를 만드는 함수

const counterActionCreator = {
  countUp: (step: number) => ({ type: COUNTER_ACTION.UP, payload: { step } }),
  countDown: (step: number) => ({ type: COUNTER_ACTION.DOWN, payload: { step } }),
  countReset: () => ({ type: COUNTER_ACTION.RESET })
};

Action을 직접 객체로 만들 수도 있지만,
이렇게 Action Creator 함수로 만들면 재사용성이 좋아집니다.
컴포넌트에선 dispatch(counterActionCreator.countUp(3)) 이런 식으로 사용하죠.

Reducer – 상태를 실제로 바꿔주는 순수 함수

const counterReducer = (state = initialState, action: CounterAction) => {
  switch(action.type){
    case COUNTER_ACTION.UP:
      return { ...state, count: state.count + action.payload.step };
    case COUNTER_ACTION.DOWN:
      return { ...state, count: state.count - action.payload.step };
    case COUNTER_ACTION.RESET:
      return { ...state, count: 0 };
    default:
      return state;
  }
}

Reducer는 현재 상태와 액션을 받아서 새로운 상태를 반환하는 순수 함수입니다.

컴포넌트에서 상태 사용 – useSelector & useDispatch

const count = useSelector((state: RootState) => state.counterStore.count);

현재 상태를 꺼내서 화면에 표시하는 컴포넌트입니다.
useSelector로 Redux store에서 count 값을 구독합니다.
상태가 변경되면 이 컴포넌트는 자동으로 리렌더링됩니다.

const dispatch = useDispatch();

<button onClick={() => dispatch(counterActionCreator.countUp(3))}>+3</button>

버튼 클릭 시 dispatch로 액션을 전달해 상태를 변경합니다.


Redux 흐름 요약

1. 버튼 클릭 (이벤트 발생)2. Action Creator 함수 실행 → Action 객체 생성
              ↓
3. dispatch(Action) 호출 → Store로 전달
              ↓
4. Reducer가 현재 상태 + Action 보고 새 상태 생성
              ↓
5. Store 갱신 → useSelector로 구독 중인 컴포넌트 리렌더링
profile
프론트 개발을 해보아요👨🏻‍💻

0개의 댓글