본문 바로가기

카테고리 없음

[React] React로 할 일 관리 앱 만들기: 7장 - Redux로 상태 관리 개선하기

반응형

 

안녕하세요! 이번 포스트에서는 우리의 할 일 관리 앱에 Redux를 도입하여 상태 관리를 개선하는 방법을 알아보겠습니다. Redux는 React 애플리케이션의 상태를 효율적으로 관리할 수 있게 해주는 강력한 라이브러리입니다.

1. Redux 설치하기

먼저 필요한 패키지들을 설치합니다. 터미널에서 다음 명령어를 실행하세요:

npm install redux react-redux @reduxjs/toolkit

2. Redux 스토어 설정하기

src 폴더에 store 폴더를 만들고, 그 안에 todoSlice.js 파일을 생성합니다:

// src/store/todoSlice.js
import { createSlice } from '@reduxjs/toolkit';

const todoSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
      state.push({ id: Date.now(), text: action.payload, completed: false });
    },
    toggleTodo: (state, action) => {
      const todo = state.find(todo => todo.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
    deleteTodo: (state, action) => {
      return state.filter(todo => todo.id !== action.payload);
    },
    editTodo: (state, action) => {
      const todo = state.find(todo => todo.id === action.payload.id);
      if (todo) {
        todo.text = action.payload.text;
      }
    },
  },
});

export const { addTodo, toggleTodo, deleteTodo, editTodo } = todoSlice.actions;

export default todoSlice.reducer;

그리고 store.js 파일을 생성합니다:

// src/store/store.js
import { configureStore } from '@reduxjs/toolkit';
import todoReducer from './todoSlice';

const store = configureStore({
  reducer: {
    todos: todoReducer,
  },
});

export default store;

3. Redux Provider 설정하기

src/index.js 파일을 수정하여 Redux Provider를 설정합니다:

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

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

4. 컴포넌트 수정하기

이제 각 컴포넌트를 수정하여 Redux를 사용하도록 합니다.

TodoList 컴포넌트

// src/components/TodoList.js
import React from 'react';
import { useSelector } from 'react-redux';
import TodoForm from './TodoForm';
import TodoItem from './TodoItem';
import TodoFilters from './TodoFilters';
import TodoStats from './TodoStats';
import './TodoList.css';

function TodoList() {
  const todos = useSelector(state => state.todos);
  const [filter, setFilter] = React.useState('all');

  const filteredTodos = todos.filter((todo) => {
    if (filter === 'active') return !todo.completed;
    if (filter === 'completed') return todo.completed;
    return true;
  });

  return (
    <div className="todo-app">
      <h2>할 일 관리 앱</h2>
      <TodoForm />
      <TodoFilters filter={filter} setFilter={setFilter} />
      <ul className="todo-list">
        {filteredTodos.map((todo) => (
          <TodoItem key={todo.id} todo={todo} />
        ))}
      </ul>
      <TodoStats todos={todos} />
    </div>
  );
}

export default TodoList;

TodoForm 컴포넌트

// src/components/TodoForm.js
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { addTodo } from '../store/todoSlice';

function TodoForm() {
  const [inputValue, setInputValue] = useState('');
  const dispatch = useDispatch();

  const handleSubmit = (e) => {
    e.preventDefault();
    if (inputValue.trim() !== '') {
      dispatch(addTodo(inputValue));
      setInputValue('');
    }
  };

  return (
    <form className="todo-form" onSubmit={handleSubmit}>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        placeholder="새로운 할 일을 입력하세요"
      />
      <button type="submit">추가</button>
    </form>
  );
}

export default TodoForm;

TodoItem 컴포넌트

// src/components/TodoItem.js
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { toggleTodo, deleteTodo, editTodo } from '../store/todoSlice';

function TodoItem({ todo }) {
  const [isEditing, setIsEditing] = useState(false);
  const [editValue, setEditValue] = useState(todo.text);
  const dispatch = useDispatch();

  const handleEdit = () => {
    dispatch(editTodo({ id: todo.id, text: editValue }));
    setIsEditing(false);
  };

  return (
    <li className="todo-item">
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => dispatch(toggleTodo(todo.id))}
      />
      {isEditing ? (
        <input
          type="text"
          value={editValue}
          onChange={(e) => setEditValue(e.target.value)}
          onBlur={handleEdit}
          autoFocus
        />
      ) : (
        <span
          style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          onDoubleClick={() => setIsEditing(true)}
        >
          {todo.text}
        </span>
      )}
      <button onClick={() => dispatch(deleteTodo(todo.id))}>삭제</button>
    </li>
  );
}

export default TodoItem;

TodoStats 컴포넌트

// src/components/TodoStats.js
import React from 'react';
import { useSelector } from 'react-redux';

function TodoStats() {
  const todos = useSelector(state => state.todos);
  const totalTodos = todos.length;
  const completedTodos = todos.filter((todo) => todo.completed).length;
  const activeTodos = totalTodos - completedTodos;

  return (
    <div className="todo-stats">
      <p>전체 할 일: {totalTodos}</p>
      <p>완료한 할 일: {completedTodos}</p>
      <p>남은 할 일: {activeTodos}</p>
    </div>
  );
}

export default TodoStats;

5. 로컬 스토리지 연동

Redux 상태를 로컬 스토리지와 연동하기 위해 src/store/store.js 파일을 수정합니다:

// src/store/store.js
import { configureStore } from '@reduxjs/toolkit';
import todoReducer from './todoSlice';

const loadState = () => {
  try {
    const serializedState = localStorage.getItem('todos');
    if (serializedState === null) {
      return undefined;
    }
    return JSON.parse(serializedState);
  } catch (err) {
    return undefined;
  }
};

const saveState = (state) => {
  try {
    const serializedState = JSON.stringify(state);
    localStorage.setItem('todos', serializedState);
  } catch {
    // Ignore write errors
  }
};

const preloadedState = { todos: loadState() };

const store = configureStore({
  reducer: {
    todos: todoReducer,
  },
  preloadedState,
});

store.subscribe(() => {
  saveState(store.getState().todos);
});

export default store;

실행 결과

이제 npm start 명령어로 앱을 실행하면, Redux를 사용하여 상태를 관리하는 할 일 관리 앱을 볼 수 있습니다.

마무리

이번 챕터에서는 Redux를 도입하여 앱의 상태 관리를 개선했습니다. Redux를 사용함으로써 얻을 수 있는 장점은 다음과 같습니다:

  1. 중앙 집중식 상태 관리: 모든 상태를 한 곳에서 관리할 수 있습니다.
  2. 예측 가능한 상태 변화: 액션과 리듀서를 통해 상태 변화를 명확하게 정의할 수 있습니다.
  3. 디버깅 용이성: Redux DevTools를 사용하여 상태 변화를 쉽게 추적할 수 있습니다.
  4. 시간 여행 디버깅: 이전 상태로 쉽게 돌아갈 수 있습니다.

Redux를 사용하면서 느낀 점이 있나요? 상태 관리가 더 쉬워졌나요, 아니면 더 복잡해졌나요? 여러분의 경험을 댓글로 공유해 주세요!

다음 챕터에서는 비동기 작업 처리와 미들웨어 사용 방법에 대해 알아보겠습니다. React와 Redux로 앱을 개발하면서 궁금한 점이 있다면 언제든 질문해 주세요!

반응형