반응형
안녕하세요! 이번 포스트에서는 우리의 할 일 관리 앱에 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를 사용함으로써 얻을 수 있는 장점은 다음과 같습니다:
- 중앙 집중식 상태 관리: 모든 상태를 한 곳에서 관리할 수 있습니다.
- 예측 가능한 상태 변화: 액션과 리듀서를 통해 상태 변화를 명확하게 정의할 수 있습니다.
- 디버깅 용이성: Redux DevTools를 사용하여 상태 변화를 쉽게 추적할 수 있습니다.
- 시간 여행 디버깅: 이전 상태로 쉽게 돌아갈 수 있습니다.
Redux를 사용하면서 느낀 점이 있나요? 상태 관리가 더 쉬워졌나요, 아니면 더 복잡해졌나요? 여러분의 경험을 댓글로 공유해 주세요!
다음 챕터에서는 비동기 작업 처리와 미들웨어 사용 방법에 대해 알아보겠습니다. React와 Redux로 앱을 개발하면서 궁금한 점이 있다면 언제든 질문해 주세요!
반응형