Redux를 이용하여 TodoApp을 구현하기에 앞서 Redux는 무엇이며 왜 필요한지 알아봅시다.
Redux란?
전역의 state를 관리할 수 있도록 도와주는 전역 상태관리 라이브러리입니다.
왜 Redux를 사용할까?
기존에 상위 컴포넌트에서 생성한 state를 하위 컴포넌트로 prop을 통해 state를 전달하였습니다.
하지만 중간 컴포넌트에서 해당 state가 필요 없어도 단순히 하위 컴포넌트를 위해 중간 컴포넌트를 거쳐야 하는 불편함이 있었습니다.
Redux를 사용하면 전역으로 상태를 관리하기 때문에 props로 일일히 내려줘야 하는 불편함을 해소할 수 있습니다.
그럼 Context API는?
Context API 또한 전역 상태 관리 도구입니다. 하지만 Context API는 상태가 변경될 때 Provider 하위의 모든 컴포넌트를 리렌더링하기 때문에 불필요한 업데이트를 막기 위해 복잡한 최적화가 필요합니다.
하지만 Redux는 상태 변경 시 관련된 컴포넌트만 선택적으로 업데이트할 수 있어 최적화에 유리합니다.
시작하기에 앞서 아래의 명령어를 통해 라이브러리를 설치합니다.
yarn add redux react-redux
폴더 구조
/src
├── /redux
│ ├── /config
│ │ ├── configStore.js # Redux store 설정
│ │
│ ├── /modules
│ │ ├── todos.js # todos 관련 액션 및 리듀서
- configStore.js : 전역 state를 관리하는 Store를 만드는 설정 코드들이 있는 파일
- todos.js : todo-list에 필요한 state들이 모여 있는 파일
configStore.js
import { createStore } from "redux";
import { combineReducers } from "redux";
const rootReducer = combineReducers({});
const store = createStore(rootReducer);
export default store;
combineReducers()를 이용해 여러 개의 reducer의 반환 값을 하나의 상태 객체로 묶어줍니다.
createStore()를 통해 해당 리듀서들을 관리하는 store를 생성합니다.
main.jsx
import { Provider } from 'react-redux';
import store from './redux/config/configStore.js';
createRoot(document.getElementById('root')).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>
);
Provider를 통해 App 내의 모든 하위 컴포넌트들이 store에 접근할 수 있도록 만들어줍니다.
todos.js
const initialState = [
{ id: 1, title: '리액트 배우기', isDone: false },
{ id: 2, title: '리덕스 배우기', isDone: false },
];
const todos = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
export default todos;
initialState는 useState()를 통해 초기값을 정해주는 것과 같습니다.
todos는 Reducer로 변화를 일으키는 함수입니다. setTodo()와 같은 역할을 합니다.
모듈을 생성하였으면
const rootReducer = combineReducers({todos: todos});
스토어와 모듈을 연결시켜줍니다.
이제 컴포넌트에서 연결이 되었는지 확인해 봅시다.
useSelector()
// App.jsx
import { useSelector } from 'react-redux';
function App(){
const todos = useSelector((state) => state);
console.log(todos);
return ...
}

useSelector()의 콜백함수에서 꺼낸 state는 모든 리덕스 모듈의 객체를 의미하는 Root State인 것을 확인할 수 있습니다.
Redux는 상태 변경 시 관련된 컴포넌트만 선택적으로 업데이트할 수 있어 최적화에 유리합니다. 이 구문이 기억나시나요?
위처럼 모든 state를 반환하여 사용하면 불필요한 렌더링이 발생합니다.

지금은 연동 유무를 확인하기 위해 모든 state를 반환했지만 특정 state만을 반환해야 합니다.
const todos = useSelector((state) => state.todos);
todo의 상태를 변경하기 전 Redux의 흐름은 어떤지 알아봅시다.
Redux 핵심, Part 1: Redux Overview and Concepts | Redux
The official Essentials tutorial for Redux: learn how to use Redux, the right way
ko.redux.js.org
할 일을 추가했을 때를 예로 진행하겠습니다.

- UI에서 이벤트 발생 → 사용자가 할 일을 입력한 후 ADD 버튼을 클릭합니다.
- Action(액션) 객체 생성 → dispatch({ type: 'ADD_TODO', payload: todo })
- Reducer(리듀서) 호출 → todos reducer가 현재 상태와 액션을 받아 새로운 상태를 반환합니다.
- Store가 상태 업데이트 → 변경된 상태를 저장하고 구독된 컴포넌트에 알립니다.
- React 컴포넌트가 변경된 state를 받아 UI를 다시 렌더링
ADD_TODO에 따른 reducer를 구현해봅시다.
const ADD_TODO = 'ADD_TODO';
export const addTodo = (payload) => ({
type: ADD_TODO,
payload
})
const todos = (state = initialState, action) => {
console.log(action);
switch (action.type) {
case ADD_TODO:
return [...state, {id: new Date().getTime(), title: action.payload, isDone: false}];
default:
return state;
}
};
ADD 버튼을 클릭했을 때의 action 객체를 생성하는 코드입니다.
import { useDispatch, useSelector } from 'react-redux';
import { addTodo } from './redux/modules/todos';
function App() {
const todos = useSelector((state) => state.todos);
const dispatch = useDispatch();
const handleAddBtn = () => {
dispatch(addTodo(todo));
}
return ...
}
export default App
이제 버튼을 눌러 action 객체가 잘 전달되는지 확인해봅시다.

이렇게 reducer가 action을 받아 새로운 상태를 반환하면 Store가 변경된 todo를 저장하고 App 컴포넌트에서는 변경된 todo를 받아 UI를 리렌더링하게 됩니다.
Redux Toolkit이 오늘날 Redux를 사용하는 방법인 이유 | Redux
소개 > RTK가 현재의 Redux인 이유: RTK가 어떻게 Redux 코어를 대체하는지에 대한 상세 내용
ko.redux.js.org
Redux Toolkit (RTK)은 Redux 애플리케이션을 작성하는 데 필요한 많은 복잡한 작업을 단순화해 줍니다. RTK는 스토어 설정, 리듀서 생성, 불변성 유지 등 복잡한 기능을 처리하여, 개발자가 반복적인 코드를 줄이고 더 간결하게 Redux를 사용할 수 있도록 도와줍니다. 이는 Redux의 초기 방식이 제공하지 않았던 유용한 기능을 제공하며, 모던 Redux를 사용하는 데 더 적합한 방법으로 자리잡았습니다.
이와 같은 이유로 오늘날 Redux 관리자들 또한 Redux Toolkit을 사용하기를 권장하고 있습니다. 다음 블로그는 Redux를 통해 완성된 ToDoApp을 Redux Toolkit으로 변경하는 것으로...