본문 바로가기
SAC

[새싹x코딩온] 풀스택 개발자 부트캠프 과정 13 주차-2 React Redux 상태관리

by 비너발트 2025. 2. 13.

 

📌 상태관리! 도대체 뭘까?

이전 React에서도 말했던 상태관리! 개념을 다시 집고 넘어가 봅시다!

React는 컴포넌트 단위로 UI를 구성합니다 컴포넌트 단위로 UI를 구성하는 이유는 바로 재사용성때문입니다

이렇게 컴포넌트는 HTML, CSS, Javascript 로직을 하나의 단위로 묶어 관리할 수있습니다

 

버튼 하나를 갖고있는 컴포넌트를 만들자면 아래와 같습니다

function Button() {
  return <button>Click Me</button>;
}

 

그렇다면 컴포넌트와 상태관리가 어떤 연관을 갖고있을까?🤔

상태는 컴포넌트 내부에서 관리되는 값(데이터) 입니다

리액트는 컴포넌트의 상태(값)을 변경하게되면 해당 컴포넌트를 다시 렌더링합니다!

 

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0); // 상태 선언함!

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button> //버튼을 클릭할때마다 count 상태값을 setCount 로변경!
    </div>
  );
}

 

즉 상태(state)는 리액트 컴포넌트 내부에서 선언되는 값으로, 컴포넌트의 UI를 동적으로 변경하는 역할을 합니다. 상태가 변경되면 해당 상태를 사용하는 컴포넌트만 다시 렌더링되며, 전체 DOM을 다시 그리는 것이 아니라 가상 DOM을 비교 하여 필요한 부분만 업데이트하기 때문에 성능 최적화에 유리하고 재사용하기 쉬운 컴포넌트를 만들 수 있습니다

 

 

 

상태관리와 Redux의 관계는 뭘까?

리액트의 상태와 Redux는 모두 상태 변경을 관리하는 역할을 한다는 점에서 공통점이 있고
useState와 useReducer를 사용하여 개별 컴포넌트 또는 컴포넌트 간의 상태를 관리할 수 있습니다

 

하지만 프로젝트 규모가 커지면 상태를 여러 컴포넌트에 전달하는 것이 점점 복잡해지는 문제가 발생하는데
리액트의 컴포넌트는 계층 구조를 가지는데, 최상위(root) 컴포넌트에서 최하위 자식 컴포넌트까지 상태를 전달하려면
중간에 있는 모든 컴포넌트가 상태를 거쳐야 하는 "props drilling" 문제가 발생할 수 있습니다

 

props drilling을 발생시키지 않으려면 상태를 전역화하여 관리해야합니다 이때 방법은

리액트의 기본 훅 useContext를 사용하거나 Redux및 다른 상태관리 라이브러리를 사용하는 방법이 있습니다

 

 

useContext VS Redux

 

[useContext]

  • useContext는 리액트에서 기본적으로 제공하는 훅으로 별도의 설치가 필요없습니다.
  • 비교적 Redux보다 간결한 코드로 상태를 공유할 수 있습니다.
  • useState와 useReducer로 상태를 업데이트합니다.
  • 여러개의 Context를 관리하게되면 Provider 중첩 문제가 발생합니다.
  • 상태 관리 규모가 커질수록 여러 개의 Context를 사용해야 하므로 구조적으로 관리가 어려워질 수 있습니다.

 

[Redux]

  • 별도의 redux 라이브러리 설치를 해야합니다.
  • useContext보다 초기화 하는 코드가 많습니다. (Toolkit 사용시 간소화가능)
  • Action, Reducer 로 상태를 업데이트합니다.
  • 규모가 커질수록 useContext보다 유지보수와 상태 관리가 더 체계적으로 가능해집니다.

 

Redux는 사용할때 액션, 리듀서, 스토어 사용한 간단한 예제입니다

// 액션.js
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";

export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });
// 리듀서.js
import { INCREMENT, DECREMENT } from "./actions";

const initialState = { count: 0 };

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 };
    case DECREMENT:
      return { count: state.count - 1 };
    default:
      return state;
  }
};

export default counterReducer;
// 스토어.js
import { createStore } from "redux";
import counterReducer from "./reducer";

const store = createStore(counterReducer);

export default store;

 

// App.js
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement } from "./actions";

function App() {
  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>카운터: {count}</h1>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
}

export default App;