본문 바로가기

Libraries/Redux

[Redux] Redux로 프로젝트 시작하는 법 (간단한 Todo 프로젝트 만들어보기)

728x90

 

 

Redux랑 React 연동해서 간단한 프로젝트를 만들어볼 것이다.

 


 

 

* 필자는 Vite를 사용하여 React 개발 환경을 설정하였다. (참고 ) https://dev-ini.tistory.com/58)

* Toolkit은 사용하지 않은 버전이다. 추후 학습 시 Toolkit을 사용한 버전을 업로드 예정!

 

 

 1.  명령어로 Redux와 React 패키지를 설치해준다. 

npm install --save redux react-redux

 

 

 2. 설치하고 나면, package.json에 아래와 같이 react-redux가 의존성 목록(dependencies)에 추가된 것을 볼 수 있다. 

 

 

 

 3. Redux에 필요한 폴더와 파일을 만든다. 

 

 (1) src 폴더 안에 redux 폴더 만들기 

 

 

 (2) redux 폴더 안에 store.js 만들기 

store.js 파일은 redux store를 생성하고 export하는 코드가 들어갈 파일이다.

 

 

 (3) redux 폴더 안에 reducers 폴더 만들기 

이 폴더 안에 Redux의 reducer들이 들어갈 것이다.

 

 

 (4) reducers 폴더 안에 index.js 파일 만들기 

index.js 파일은 root reducer가 들어가게될것이다.

 

 

 (5) index.js에 다음과 같이 적는다. 

index.js

import { combineReducers } from "redux";

const rootReducer = combineReducers({});

export default rootReducer;

현재는 Reducer가 하나도 없기 때문에 빈객체를 넣었다.

 

 

 (6) store.js에 다음과 같이 적는다. 

rootReducer를 import하고 그걸 createStore 함수에 파라미터로 넣고  호출하여 store를 생성한다.
그리고 생성된 스토어를 export default해준다.

store.js

import { createStore } from "redux";
import rootReducer from "./reducers";

const store = createStore(rootReducer);

export default store;

 

 

VsCode에서는 이렇게 줄이 그어지는데, deprecated 되었기 때문이다. 현재는 Redux Toolkit을 사용하는 것이 리덕스 개발의 표준 방법이기 때문에 이렇게 createStore 함수를 사용하는 방식은 과거의 유산으로 남았다. 
일단 여기서는 이렇게 과거의 방식을 먼저 학습하고, 그 이후에 최신 방법을 적용하게 될 것이다.

 

 

 (7) src 안에 있는 App.jsx와 main.jsx를 수정한다. 

리덕스의 provider는 store를 props로 가진다.
App 컴포넌트가 Provider로 감싸져서 store에 접근할 수 있게된다.

main.jsx

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

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

 

App.jsx

여기 안에 컴포넌트들을 렌더링 하게 될 것이다!

function App() {
  return (
    <div>
      <h1>My Redux Todo App</h1>
    </div>
  );
}

export default App;

 

 

 

 4. Todo 어플리케이션 만들기 

 

 (1) src 폴더 안에 components 폴더를 만들고, 그 안에 TodoApp.js 파일을 만든다. 

TodoApp.js

import { useState } from "react";

function TodoApp() {
  const [newTodo, setNewTodo] = useState("");

  return (
    <div>
      <h3>오늘 할 일</h3>
      <ul></ul>

      <div>
        <input
          value={newTodo}
          onChange={(event) => {
            setNewTodo(event.target.value);
          }}
        />
        <button>할 일 추가</button>
        <button>할 일 삭제</button>
        <button>모두 삭제</button>
      </div>
    </div>
  );
}

export default TodoApp;

 

 

 (2) src > App.jsx 파일에 <TodoAppContainer /> 컴포넌트(Redux와 연결된 버전인 connect()된 컴포넌트)를 import해서 렌더링 한다. 

import TodoAppContainer from "./redux/containers/TodoAppContainer"; // Redux와 연결된 컴포넌트 가져오기

function App() {
  return (
    <div>
      <h1>My Redux Todo App</h1>
      <TodoAppContainer /> 
    </div>
  );
}

export default App;

 

 

 (3) redux 폴더 밑에 action.js 파일을 만든다. 

이 파일은 action type과 action creator들을 모아서 관리하는 파일이다. 

action.js

// TODO 관련 Action Type
export const ACTION_TYPE_ADD_TODO = "ADD_TODO";
export const ACTION_TYPE_REMOVE_TODO = "REMOVE_TODO";
export const ACTION_TYPE_REMOVE_ALL = "REMOVE_ALL";

// Action Creator : Action 객체를 생성하는 함수
export function addTodoActionCreator(text) { // 할일 추가
    return {
        type: ACTION_TYPE_ADD_TODO,
        text: text
    }
}

export function removeTodoActionCreator() { // 할일 삭제
    return {
        type: ACTION_TYPE_REMOVE_TODO,
    }
}

export function removeAllActionCreator() { // 모두 삭제
    return {
        type: ACTION_TYPE_REMOVE_ALL,
    }
}

 

 

 (4) reducers 폴더 안에 todoReducers.js 파일을 만든다. 

todo와 관련된 action들을 처리하기 위한 reducer 코드를 작성한다.

todoReducers.js

import {
  ACTION_TYPE_ADD_TODO,
  ACTION_TYPE_REMOVE_TODO,
  ACTION_TYPE_REMOVE_ALL
} from './../action';  // TODO 관련 Action Type을 import

// 초기화
const initialState = [];

// todoReducer 함수: 상태가 어떻게 변경될지를 정의하는 함수이다.
function todoReducer(state = initialState, action) {
  switch (action.type) {
      case ACTION_TYPE_ADD_TODO:
          return state.concat(action.text);
      case ACTION_TYPE_REMOVE_TODO:
          return state.slice(0, -1);
      case ACTION_TYPE_REMOVE_ALL:
          return [];
      default:
          return state;
  }
}

export default todoReducer;

 

 

 (5) reducers 폴더 안의 index.js 파일에서 todo Reducer를 import해서 combineReducers 함수에 파라미터로 넣어준다. 

import { combineReducers } from "redux";
import todoReducer from "./todoReducers";


// combineReducers는 여러개의 Reducer를 하나로 합치는 역할 = rootReducer
const rootReducer = combineReducers({
    todo: todoReducer, // 각 state에 접근하기 위한 키 : todoReducer
});

export default rootReducer;

 

 

(6) redux 폴더 아래에 containers 폴더를 만들고, TodoAppContainer.js 파일을 만든다.

react redux 패키지의 connect함수를 사용해서 container를 만드는 코드를 작성한다. 

import { connect } from "react-redux";
import {
  addTodoActionCreator,
  removeTodoActionCreator,
  removeAllActionCreator,
} from "../action"
import TodoApp from "../../components/TodoApp";

// state를 리액트 컴포넌트의 props로 연결시켜주는 역할을 하는 함수 // ownProps는 옵션임
function mapStateToProps(state) {
  return {
    todoItems: [...state.todo, ...(state.fetchTodos?.data || [])],
  };
}

// dispatch를 리액트 컴포넌트의 props로 연결시켜주는 역할을 하는 함수 // ownProps는 옵션임
function mapDispatchToProps(dispatch) {
  return {
    addTodo: (text) => {
      dispatch(addTodoActionCreator(text));
    },
    removeTodo: () => {
      dispatch(removeTodoActionCreator());
    },
    removeAll: () => {
      dispatch(removeAllActionCreator());
    },
  };
}

// 두개의 함수를 connect 함수의 인자로 넣어서 호출하고, (이때 Wrapper Function가 return되는데 이걸 사용하면 어떤 컴포넌트든지 Container 컴포넌트로 만들 수 있음)
// Wrapper Function을 통해 Redux와 TodoApp컴포넌트를 연결하면 Container 컴포넌트가 return된다.
// Container는 리덕스의 일부 state와 dispatch를 포함하는 리액트 컴포넌트임

export default connect(mapStateToProps, mapDispatchToProps)(TodoApp);

 

 

 (7) TodoApp 컴포넌트에 redux로 받아온 state와 dispatch를 연동하는 작업을 하고, <ul>에 목록을 추가하고, <button>에 onClick이벤트를 달아준다. 

import { useState } from "react";

function TodoApp() {
  const {
    // Redux state
    todoItems,
    // Redux dispatch
    addTodo,
    removeTodo,
    removeAll,
    triggerAsyncFunction,
    fetchTodo,
  } = props; // props를 통해서 가져올 수 있는 이유는 TodoApp을 리덕스에 connect 시켰기 때문이다.

  const [newTodo, setNewTodo] = useState("");

  return (
    <div>
      <h3>오늘 할 일</h3>
      {/* 할일 목록 */}
      <ul>
        {todoItems.map((todoItem, index) => {
          return <li key={index}>{todoItem}</li>;
        })}
      </ul>

      <div>
        <input
          value={newTodo}
          onChange={(event) => {
            setNewTodo(event.target.value);
          }}
        />
        <button
          onClick={() => {
            addTodo(newTodo);
            setNewTodo("");
          }}
        >
          할 일 추가
        </button>
        <button onClick={removeTodo}>할 일 삭제</button>
        <button onClick={removeAll}>모두 삭제</button>
      </div>
    </div>
  );
}

export default TodoApp;

 

* 혹시 propTypes 오류가 뜬다면, 아래와 같이 바꿔준다

import PropTypes from "prop-types";
import { useState } from "react";

function TodoApp(props) {
  const {
    // Redux state
    todoItems,
    // Redux dispatch
    addTodo,
    removeTodo,
    removeAll,
  } = props; // props를 통해서 가져올 수 있는 이유는 TodoApp을 리덕스에 connect 시켰기 때문이다.

  const [newTodo, setNewTodo] = useState("");

  return (
    <div>
      <h3>오늘 할 일</h3>
      {/* 할일 목록 */}
      <ul>
        {todoItems.map((todoItem, index) => {
          return <li key={index}>{todoItem}</li>;
        })}
      </ul>

      <div>
        <input
          value={newTodo}
          onChange={(event) => {
            setNewTodo(event.target.value);
          }}
        />
        <button
          onClick={() => {
            addTodo(newTodo);
            setNewTodo("");
          }}
        >
          할 일 추가
        </button>
        <button onClick={removeTodo}>할 일 삭제</button>
        <button onClick={removeAll}>모두 삭제</button>
      </div>
    </div>
  );
}

TodoApp.propTypes = {
  todoItems: PropTypes.arrayOf(PropTypes.string).isRequired,
  addTodo: PropTypes.func.isRequired,
  removeTodo: PropTypes.func.isRequired,
  removeAll: PropTypes.func.isRequired,
  triggerAsyncFunction: PropTypes.func,
  fetchTodo: PropTypes.func,
};


export default TodoApp;

 

 

 

 5. npm run dev로 실행해보기 

 

 

728x90

'Libraries > Redux' 카테고리의 다른 글

[Redux] redux-actions  (1) 2025.02.03
[Redux] Ducks Pattern  (0) 2025.01.31
[Redux] Redux와 React를 연동하기  (1) 2025.01.18
[Redux] Reducer  (0) 2025.01.12
[Redux] Action  (0) 2025.01.12