고급 상태 관리 Redux 사용하기, 액션과 리듀서를 사용한 상태 변경

React는 컴포넌트 기반의 사용자 인터페이스를 구축하는 데 매우 효율적입니다. 그러나 애플리케이션이 복잡해짐에 따라 상태 관리는 더욱 어려워질 수 있습니다. 이럴 때 Redux와 같은 상태 관리 라이브러리를 사용하는 것이 유용합니다. Redux는 전역 상태를 중앙 집중식으로 관리하는 라이브러리로, 복잡한 React 애플리케이션에서 상태를 예측 가능하게 유지할 수 있게 도와줍니다. 이번 글에서는 Redux를 사용하여 상태를 관리하는 방법과 액션 및 리듀서를 통한 상태 변경에 관해 자세히 설명하겠습니다.

Redux란 무엇인가?

Redux는 JavaScript 애플리케이션에서 전역 상태를 관리하기 위한 라이브러리입니다. 애플리케이션의 모든 상태를 하나의 중앙 저장소(store)에 보관하며, 이 저장소는 액션(action)을 통해 상태를 업데이트합니다. Redux를 사용하면 애플리케이션의 상태 변화가 일관되고 예측 가능해지며, 유지보수와 디버깅이 쉬워집니다.

Redux의 주요 개념

  • Store: 애플리케이션의 상태를 저장하는 객체입니다. Redux에서는 오직 하나의 Store만을 사용합니다.
  • Action: 상태 변화의 목적을 설명하는 객체입니다. Action은 type 속성을 반드시 가져야 하며, 필요에 따라 추가적인 데이터를 포함할 수 있습니다.
  • Reducer: 현재 상태와 액션을 인수로 받아 새로운 상태를 반환하는 순수 함수입니다. Reducer는 상태를 변경하는 로직을 포함하고 있습니다.

Redux 설치 및 기본 설정

Redux를 설치하려면, npm 또는 yarn을 사용할 수 있습니다. 아래 명령어를 통해 Redux 및 React-Redux를 설치합니다.

npm install redux react-redux

프로젝트 구조 설정

간단한 Todo 앱의 예제를 통해 Redux를 어떻게 사용하는지 보여드리겠습니다. 아래와 같은 프로젝트 구조를 설정합니다.


my-todo-app/
|-- src/
|   |-- components/
|   |   |-- TodoList.js
|   |   |-- TodoItem.js
|   |-- actions/
|   |   |-- todoActions.js
|   |-- reducers/
|   |   |-- todoReducer.js
|   |-- store/
|   |   |-- store.js
|   |-- App.js
|   |-- index.js

액션(Action) 정의하기

액션은 상태를 업데이트하기 위한 “신호”입니다. 액션에는 반드시 type 속성이 있어야 하며, 추가적인 데이터(payload)를 가질 수 있습니다. 아래는 Todo와 관련된 액션들을 정의한 todoActions.js입니다.


// src/actions/todoActions.js

export const ADD_TODO = 'ADD_TODO';
export const REMOVE_TODO = 'REMOVE_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';

export const addTodo = (todo) => ({
    type: ADD_TODO,
    payload: todo,
});

export const removeTodo = (id) => ({
    type: REMOVE_TODO,
    payload: id,
});

export const toggleTodo = (id) => ({
    type: TOGGLE_TODO,
    payload: id,
});

리듀서(Reducer) 정의하기

리듀서는 액션을 처리하여 새로운 상태를 반환하는 순수 함수입니다. 아래는 Todo 리듀서의 구현 예시입니다.


// src/reducers/todoReducer.js

import { ADD_TODO, REMOVE_TODO, TOGGLE_TODO } from '../actions/todoActions';

const initialState = {
    todos: [],
};

const todoReducer = (state = initialState, action) => {
    switch (action.type) {
        case ADD_TODO:
            return {
                ...state,
                todos: [...state.todos, { id: Date.now(), text: action.payload, completed: false }],
            };
        case REMOVE_TODO:
            return {
                ...state,
                todos: state.todos.filter(todo => todo.id !== action.payload),
            };
        case TOGGLE_TODO:
            return {
                ...state,
                todos: state.todos.map(todo =>
                    todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
                ),
            };
        default:
            return state;
    }
};

export default todoReducer;

스토어(Store) 설정하기

Redux에서 스토어는 애플리케이션의 상태를 저장하고 액션을 디스패치하는 역할을 합니다. 아래는 스토어 설정을 위한 예제입니다.


// src/store/store.js

import { createStore } from 'redux';
import todoReducer from '../reducers/todoReducer';

const store = createStore(todoReducer);

export default store;

React 앱과 Redux 연동하기

React 애플리케이션에 Redux 스토어를 연결하기 위해서는 Provider 컴포넌트를 사용해야 합니다. Provider 컴포넌트는 애플리케이션 최상위에서 Redux의 스토어를 전달합니다.


// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store/store';
import App from './App';

ReactDOM.render(
    
        
    ,
    document.getElementById('root')
);

컴포넌트에서 Redux 상태와 액션 사용하기

이제 Redux 스토어를 React 컴포넌트와 연결하여 상태를 읽고 액션을 디스패치할 수 있습니다. 아래는 Todo 리스트를 구현한 컴포넌트입니다.


// src/components/TodoList.js

import React, { useState } from 'react';
import { connect } from 'react-redux';
import { addTodo, removeTodo, toggleTodo } from '../actions/todoActions';
import TodoItem from './TodoItem';

const TodoList = ({ todos, addTodo, removeTodo, toggleTodo }) => {
    const [inputValue, setInputValue] = useState('');

    const handleAddTodo = () => {
        if (inputValue) {
            addTodo(inputValue);
            setInputValue('');
        }
    };

    return (
        

Todo List

setInputValue(e.target.value)} />
    {todos.map(todo => ( ))}
); }; const mapStateToProps = (state) => ({ todos: state.todos, }); const mapDispatchToProps = { addTodo, removeTodo, toggleTodo, }; export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

TodoItem 컴포넌트 구현하기

아래는 개별 Todo 항목을 나타내는 컴포넌트입니다. 사용자가 Todo 항목을 클릭하면 완료 상태가 토글됩니다.


// src/components/TodoItem.js

import React from 'react';

const TodoItem = ({ todo, removeTodo, toggleTodo }) => {
    return (
        
  • {todo.text}
  • ); }; export default TodoItem;

    최종 결과

    위의 코드 구조를 통해 간단한 Todo 애플리케이션을 만들었습니다. Redux를 사용하여 상태를 중앙 집중식으로 관리하게 되면, 상태의 변화가 매우 명확해지고, 애플리케이션의 유지보수가 수월해집니다. 또한, Redux DevTools를 활용하면 상태 변화의 기록을 쉽게 추적할 수 있어 디버깅이 용이합니다.

    Redux의 장점과 단점

    장점

    • 예측 가능한 상태 관리: 상태의 변화가 특정 액션에 명확하게 연결됩니다.
    • 디버깅 용이: Redux DevTools를 통해 상태 변경을 추적할 수 있습니다.
    • 상태의 중앙 집중화: 여러 컴포넌트에서 동일한 상태에 접근할 수 있습니다.

    단점

    • 보일러플레이트 코드: 액션과 리듀서를 정의해야 하므로 코드 양이 증가합니다.
    • 학습 곡선: 초기 설정이 복잡할 수 있으며, Redux의 개념을 이해하는 데 시간이 필요합니다.

    결론

    Redux는 React 애플리케이션의 상태 관리를 위한 강력한 도구입니다. 본 글에서는 Redux의 기본 개념, 액션과 리듀서를 활용한 상태 변경, 그리고 React와의 결합 방법을 설명했습니다. 실제 프로젝트에서 Redux를 사용하면 상태 관리가 훨씬 수월해지며, 애플리케이션의 구조를 더욱 효율적으로 유지할 수 있습니다.

    앞으로도 Redux와 관련된 고급 기능들, 예를 들어 미들웨어, 비동기 작업 처리 등을 다룰 예정이니 관심을 가지고 지켜봐 주시기 바랍니다.