프로그래밍/ReactJS

Redux 액션: 예측 가능한 상태 관리의 핵심 열쇠

shimdh 2025. 10. 18. 00:12
728x90

Redux를 사용하는 React 애플리케이션에서 상태 관리를 다룰 때, 처음에는 복잡하고 혼란스러울 수 있습니다. 컴포넌트 간 데이터 흐름이 뒤엉키고, 상태 변경이 예기치 않게 발생하는 경우가 많죠. 하지만 Redux 액션을 제대로 이해하면, 마치 퍼즐 조각을 하나씩 맞추는 듯 모든 것이 명확해집니다. 이 글에서는 Redux 액션이 무엇인지, 왜 필수적인지, 그리고 실전에서 어떻게 효과적으로 활용할 수 있는지 단계적으로 탐구해 보겠습니다. 초보자부터 중급 개발자까지, Redux의 핵심을 재발견할 수 있도록 예시와 팁을 추가로 보강했습니다.

Redux 액션이란 무엇인가요?

Redux 액션은 간단히 말해 "무엇이 일어났는지"를 설명하는 평범한 JavaScript 객체입니다. 이 객체는 Redux 스토어에 상태 변경 의도를 전달하며, 항상 type이라는 필수 속성을 포함합니다. type은 액션의 고유한 식별자로, 리듀서가 이 액션을 받아 상태를 어떻게 업데이트할지 결정하는 핵심 역할을 합니다.

액션은 Redux의 "단방향 데이터 흐름" 원칙을 지탱하는 기반입니다. 사용자가 버튼을 클릭하거나 API 응답이 도착할 때마다, 이러한 이벤트를 액션으로 변환하여 스토어에 "알림"을 보내는 거죠. 이 과정이 없으면 상태 관리가 무질서해질 수 있습니다.

728x90

액션의 기본 구조

대부분의 액션은 최소 두 가지 주요 속성을 가집니다:

  1. Type: 액션의 종류를 나타내는 문자열 상수. 명확하고 일관된 이름을 사용하세요. 예: 'ADD_TODO', 'DELETE_ITEM', 'FETCH_USER_SUCCESS'. 이는 코드 리뷰 시 "이 액션이 무슨 일을 하는지" 한눈에 파악할 수 있게 합니다.
  2. Payload (선택 사항): 작업에 필요한 추가 데이터. 문자열, 숫자, 객체 등 자유롭게 포함할 수 있으며, 상태 변경의 세부 정보를 전달합니다. 예를 들어, 사용자 정보를 업데이트하는 액션이라면 사용자 ID나 이름이 여기에 들어갑니다.

실전 예시: 간단한 할 일 추가 액션

아래는 할 일 앱에서 새로운 항목을 추가하는 액션 예시입니다. 이 코드를 보면 액션이 어떻게 "이벤트"를 캡처하는지 알 수 있습니다.

const ADD_TODO = 'ADD_TODO';

const addTodoAction = {
  type: ADD_TODO,
  payload: {
    id: 1,
    text: 'Redux 액션에 대해 알아보기',
    completed: false,  // 추가 속성: 완료 여부 (부족한 부분 보강)
  },
};

이 액션을 dispatch(addTodoAction)으로 스토어에 보내면, 리듀서가 type을 확인하고 payload의 데이터를 기존 상태에 반영합니다. completed 필드를 추가한 이유는 실전 앱에서 자주 필요한 상태를 미리 고려하기 위함입니다.

왜 Redux 액션을 사용해야 할까요?

Redux 액션은 단순한 객체가 아닙니다. 그것은 상태 관리를 효율적이고 예측 가능하게 만드는 강력한 도구입니다. 아래는 주요 이점입니다. (원본에 더 구체적인 예시를 추가해 설명하겠습니다.)

  1. 관심사의 분리 (Separation of Concerns)
    액션을 리듀서와 분리하면 코드가 모듈화됩니다. 예를 들어, UI 컴포넌트는 액션만 디스패치하고, 리듀서는 상태 업데이트만 담당합니다. 이는 대규모 앱에서 버그 추적을 쉽게 하며, 팀 협업 시 "이 부분은 UI, 저 부분은 로직"으로 역할을 명확히 합니다. 결과적으로 유지보수 비용이 30-50% 줄어들 수 있습니다.
  2. 예측 가능성 (Predictability)
    모든 상태 변경은 액션을 통해 발생하므로, Redux DevTools로 "시간 여행 디버깅"이 가능합니다. 예: "이 액션이 왜 상태를 망쳤을까?"를 재생하며 확인할 수 있죠. 이는 프로덕션 환경에서 예상치 못한 버그를 최소화합니다.
  3. 미들웨어 호환성 (Middleware Compatibility)
    액션은 리듀서에 도달하기 전에 미들웨어(예: redux-logger, redux-saga)로 가로채집니다. 로깅이나 인증 같은 부수적 작업을 중앙화할 수 있어, 비즈니스 로직이 복잡해지지 않습니다. 특히 redux-thunk는 비동기 처리의 표준으로 자리 잡았습니다.

이 이점들은 Redux를 "Flux 아키텍처"의 진화판으로 만드는 이유입니다. 만약 Zustand나 MobX 같은 대안을 고려 중이라면, Redux의 이러한 구조가 복잡한 앱에 더 적합할 수 있습니다.

액션 생성자(Action Creator)를 이용한 액션 생성

액션을 직접 객체로 만드는 대신 액션 생성자(Action Creator) 를 사용하는 게 베스트 프랙티스입니다. 이는 함수로, 매개변수를 받아 동적 액션을 반환합니다. 타입스크립트와 함께 사용하면 타입 안전성도 보장됩니다.

const ADD_TODO = 'ADD_TODO';

// 액션 생성자 정의
const addTodo = (id, text, completed = false) => ({
  type: ADD_TODO,
  payload: { id, text, completed },
});

// 사용 예시 (컴포넌트 내)
const handleAddTodo = () => {
  dispatch(addTodo(Date.now(), '새로운 할 일 추가하기', false));
};

액션 생성자를 사용하면 매번 객체를 하드코딩하지 않아도 되고, 테스트가 용이합니다. 팁: 상수 타입을 별도 파일로 분리해 재사용성을 높이세요.

비동기 작업 처리: Redux Thunk와 액션

현대 앱은 API 호출 같은 비동기 작업으로 가득합니다. 여기서 Redux Thunk가 빛을 발합니다. Thunk는 액션이 함수일 수 있게 해, 내부에서 비동기 로직을 처리하고 여러 액션을 디스패치합니다.

실전 예시: API에서 할 일 목록 가져오기

아래는 할 일 목록을 가져오는 Thunk 예시입니다. 요청/성공/실패 단계를 세 액션으로 나누어 상태를 세밀하게 관리합니다. (에러 핸들링과 로딩 상태를 추가 보강했습니다.)

const FETCH_TODOS_REQUEST = 'FETCH_TODOS_REQUEST';
const FETCH_TODOS_SUCCESS = 'FETCH_TODOS_SUCCESS';
const FETCH_TODOS_FAILURE = 'FETCH_TODOS_FAILURE';

// 기본 액션 생성자들
const fetchTodosRequest = () => ({ type: FETCH_TODOS_REQUEST });
const fetchTodosSuccess = (todos) => ({
  type: FETCH_TODOS_SUCCESS,
  payload: todos,
});
const fetchTodosFailure = (error) => ({
  type: FETCH_TODOS_FAILURE,
  payload: error,
});

// Thunk 액션 생성자
export const fetchTodos = () => {
  return async (dispatch) => {
    dispatch(fetchTodosRequest());  // 로딩 상태 시작
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=5');  // 실제 API 예시
      if (!response.ok) throw new Error('네트워크 오류');
      const data = await response.json();
      dispatch(fetchTodosSuccess(data));  // 성공 시 데이터 업데이트
    } catch (error) {
      console.error('할 일 가져오기 실패:', error);  // 로그 추가
      dispatch(fetchTodosFailure(error.message));  // 에러 상태 업데이트
    }
  };
};

이 구조로 리듀서는 FETCH_TODOS_REQUESTloading: true를, SUCCESS 시 데이터를 추가합니다. 결과: UI에서 로딩 스피너와 에러 메시지를 쉽게 표시할 수 있습니다. Redux Toolkit을 사용하면 이 코드를 더 간결하게 만들 수 있지만, 기본 Thunk부터 익히는 게 좋습니다.

결론: Redux 액션으로 더 나은 앱 만들기

Redux 액션은 앱의 수명 주기에서 "이벤트"를 명확히 정의함으로써 예측 가능한 동작을 보장합니다. UI와 로직의 분리, 동기/비동기 처리의 효율성 – 이는 복잡한 React 앱을 스케일링하는 데 필수입니다.

728x90