프로그래밍 일기 — Redux 기초

배우는 자(Learner Of Life)
13 min readFeb 25, 2024

--

JavaScript의 상태(State)를 저장하는 효율적인 방법

각 부분의 현재 상태(State)를 얼마나 잘 기억하고 찾아 사용할 수 있느냐가 프로그래밍의 성공을 가른다(1).

현재 열심히 마지막 게시판 프로젝트를 수행하고 있다. 그러나 퇴근하고 하기에는 시간이 조금 부족하고, 주말에만 하는데도 진전이 원하는 만큼 나지않아 답답하다. 아무래도 내가 태어나서 처음 React 프론트엔드 개발을 해보는 거여서 그런 것이겠지만, 그래도 프로그래밍을 어느 정도 해 봤다고 생각했다. 하지만 프로그래밍의 영역이 너무 방대하고, 나는 프론트 보다는 백엔드를 위주로 다뤄왔던 사람이다. 그렇기에 이러한 답답함을 감수하고, 앞으로 조금씩 나아가는 것에 감사하면서 그러나 포기하지 않고 매진하려한다.

이번 프로젝트를 진행하면서 Redux기술을 활용해 볼 필요성을 느꼈다. 아무래도 게시판 어플을 만든다고 하면, 데이터를 읽고, 쓰고, 삭제 및 수정하는 일련의 작업이 필요할텐데, 이를 할 수 있으려면 데이터를 주고 받는 구조를 확립해야한다. 데이터를 프로그램내 여러 부분들이 처리하는 과정에서 각 스테이지상의 데이터 상태(State)에 대한 정보가 필요할 수 있다. 예를 들어 내가 특정한 데이터를 검색하려고한다면, 검색바에 입력한 문자열이 데이터베이스내 저장된 특정 값과 연결된 Key값이어야하거나, 혹은 해당 문자열내 문자를 내가 찾고자하는 데이터가 포함해야한다. Key값을 바탕으로 검색하고자한다면 내가 입력한 값을 단순 문자열에서 Key값으로 변환해 주는 로직이 필요할 것이다. 반대로 Key의 개념을 쓰지않고 단순히 데이터를 직접 검색할 경우, 내가 검색할 값이 내가 입력한 문자열의 일부나 전체를 포함하는지를 검토할 수 있도록 구성해 주어야할 것이다.

물론 이 정도의 단순한 기능은 개발자가 스스로 처리로직을 모두 구현할 수 있을지도 모른다. 그러나 프로그램의 규모가 커질수록 그 안에서 처리되는 로직의 수나 기능들이 기하급수적으로 증가할 수 있다. 이 경우 상태처리를 개발자가 일일히 해주기보다는 이를 자동화할 수 있는 도구가 있다면 유용할 것이다. JavaScript에서는 바로 이 목적을 위해 나온 것이 Redux이다.

Redux(2)

Redux는 상태(State)를 효율적으로 처리할 수 있는 구조를 제공하여 코드의 유지보수성을 높이는데 도움을 주는 도구이다. 본래 Redux가 하던 역할은 React상에서 useContextuseReducr Hook의 혼용으로 해결했다. 그러나 매번 이러한 Hook을 사용하는 것보다는 이 것을 자동화할 수 없을까에 대한 고민으로 나온 것이 Redux이다. 그렇다면 Redux의 기본적인 개념은 무엇일까?

Store

Redux Store는 현재 앱의 상태(State)를 저장하는 부분이다. 앱은 항상 Store에 구독(Subscribe)되어 있다. Store내 저장된 상태정보를 앱은 변경할 수 없으나, 그 정보를 받을 수 있는 단방향 구조라 할 수 있다.

Action Type, Action, Action Creator

Action은 타입(type)속성을 갖는 그 어떤 객체(object)를 말한다. 이는 말 그대로 무엇이 일어나야 하는지를 정의한다. 상태를 어떤방식으로 변경해야하는지에 대해서는 정의하지 못한다.

Action Type은 Action의 타입 속성에 사용되는 값을 정의하는 상수(constant)이다.

Action Creator란 Action을 리턴하는 JavaScript 함수(function)를 말한다.

앱은 Action을 Dispatch한다. 이는 React의 Redux 라이브러리가 제공하는 useDispatch Hook의 메서드다. 앱은 Redux Store내 상태정보를 Action을 Dispatching하는 방식으로 변경할 수 있다.

// Action 예제
{
type: ADD_TASK
}

// Action Type 예제
export const ADD_TASK = "ADD_TASK";

// Action Creator 예제
export const addTask = (task) => {
return {
type: ADD_TASK,
payload: task,
};
};

// Dispatching 예제
dispatch(addTask(task))

Reducer

Action이 상태 변경 방식을 정의하지 못하므로, 그 역할은 Reducer가 하게된다. 이는 JavaScript의 함수로써 initialStateaction 을 입력값으로 받고 객체의 업데이트된 상태(state)정보를 리턴한다.

Action이 Dispatch되면 관련 Reducer는 Action이 정의한 행위에 따라 상태 정보를 업데이트한다. 즉, Reducer가 Action을 활용해 Store내에 있는 상태정보를 수정하는 것이다.

// Reducer 예제

// initialState 정의
const initialState = {
tasks: [],
taskTitle: "",
taskDescription: ""
};

// Reducer 정의
const taskReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TASK:
return {
...state,
tasks: [...state.tasks, action.payload],
taskTitle: "",
taskDescription: ""
};
default:
return state;
}
};
앱, Action, Reducer, Store와의 상관관계를 보여주는 그림(2)

Redux활용 방법

그렇다면 코드를 직접 활용해 Redux의 개념을 살펴보자.

  1. 먼저 npm i redux react-redux @reduxjs/toolkit 명령어를 통해 터미널에서 Redux를 설치한다.
  2. 다음으로 taskActionTypes.js 라는 파일을 정의해 Action Type을 정의한다.
  3. taskActions.js 라는 파일을 정의해 Action과 Action Creator를 정의한다.
// taskActionTypes.js
export const ADD_TASK = "ADD_TASK";
export const UPDATE_TASK_TITLE = "UPDATE_TASK_TITLE";
export const UPDATE_TASK_DESCRIPTION = "UPDATE_TASK_DESCRIPTION";

// taskActions.js
import {ADD_TASK, UPDATE_TASK_DESCRIPTION, UPDATE_TASK_TITLE} from "./taskActionTypes";

export const addTask = (task) => {
return {
type: ADD_TASK,
payload: task,
};
};

export const updateTaskTitle = (value) => {
return {
type: UPDATE_TASK_TITLE,
payload: value,
};
};

export const updateTaskDescription = (value) => {
return {
type: UPDATE_TASK_DESCRIPTION,
payload: value,
};
};

4. taskReducer.js 파일을 생성해 Reducer를 정의한다.

// taskReducer.js
import {ADD_TASK, UPDATE_TASK_DESCRIPTION, UPDATE_TASK_TITLE} from "./taskActionTypes";

const initialState = {
tasks: [],
taskTitle: "",
taskDescription: ""
};

const taskReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TASK:
return {
...state,
tasks: [...state.tasks, action.payload],
taskTitle: "",
taskDescription: ""
};
case UPDATE_TASK_TITLE:
return {
...state,
taskTitle: action.payload,
}
case UPDATE_TASK_DESCRIPTION:
return {
...state,
taskDescription: action.payload,
}
default:
return state;
}
};

export default taskReducer;

5. 이번 단순한 예제에서는 하나의 taskReducer 로 충분하나, 프로그램이 복잡해 질수록 여러 상태를 처리할 수 있어야하므로 여러 taskReducer 가 필요할 수 있다. 만약 그렇다면, rootReducer 를 생성해 여러 다른 Reducer를 병합하고 Store에 Reducer를 주입하는 방식을 활용할 수 있다.

// rootReducer.js
import {combineReducers} from "redux";
import taskReducer from "./tasks/taskReducer";

const rootReducer = combineReducers({task: taskReducer});

export default rootReducer

6. Store 생성하기

// store.js
import { configureStore } from "@reduxjs/toolkit";
import rootReducer from "./rootReducer";

const store = configureStore({ reducer: rootReducer });

export default store;

7. 제공자(Provider)를 통해 Root Component 래핑(Wrapping)

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

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

8. useDispatch()useSelector() 를 사용하여 Redux Store내 상태정보에 접근할 수 있다. useDispatch() 클래스를 통해 Dispatch 메서드에 접근하여 Action을 Dispatch할 수 있다.

// AddTasks.jsx
/* eslint-disable no-unused-vars */
import React from "react";
import {useDispatch, useSelector} from "react-redux";
import {addTask, updateTaskDescription, updateTaskTitle} from "../redux/taskActions";

const styles = {
taskContainer: {
display: "flex",
flexDirection: "column",
width: "350px"
},
mainContainer: {
textAlign: '-webkit-center'
}
}

function AddTasks() {
const dispatch = useDispatch();
const taskTitle = useSelector(state => state.task.taskTitle)
const taskDescription = useSelector(state => state.task.taskDescription)

const onAddTask = () => {
const task = {
title: taskTitle,
description: taskDescription,
}
dispatch(addTask(task))
};
const onTaskTitleChange = (e) => dispatch(updateTaskTitle(e.target.value))
const onTaskDescriptionChange = (e) => dispatch(updateTaskDescription(e.target.value))

return (
<div style={styles.mainContainer}>
<div style={styles.taskContainer}>
<input type="text" placeholder="Task Title" onChange={onTaskTitleChange} value={taskTitle} />
<input type="text" placeholder="Task Description" onChange={onTaskDescriptionChange}
value={taskDescription} />
<button onClick={onAddTask}>Add Task</button>
</div>
</div>
);
}

export default AddTasks;

여기까지 Redux의 기본 활용 방법에 대해 알아보았다. 다음 글에서는 위 방법을 실제로 활용해 Redux를 활용한 어플리케이션을 만들어보도록 한다.

참조:

(1) https://pixabay.com/photos/library-setup-books-read-stately-5219747/

(2) https://dev.to/thisurathenuka/add-redux-to-your-react-app-in-6-simple-steps-43bb

--

--

배우는 자(Learner Of Life)
배우는 자(Learner Of Life)

Written by 배우는 자(Learner Of Life)

배움은 죽을 때까지 끝이 없다. 어쩌면 그게 우리가 살아있다는 증거일지도 모른다. 배움을 멈추는 순간, 혹은 배움의 기회가 더 이상 존재하지 않는 순간, 우리의 삶은 어쩌면 거기서 끝나는 것은 아닐까? 나는 배운다 그러므로 나는 존재한다. 배울 수 있음에, 그래서 살아 있음에 감사한다.

No responses yet