React의 상태관리 (feat. Context API, Redux)

홍창현·2025년 3월 28일
1
post-thumbnail

React로 프론트엔드 개발을 하면서 가장 기본적이고도 중요한 것은 단연 상태 관리라고 생각합니다.

React에서는 상태를 관리할 수 있는 방법이 많이 있습니다.
단순한 상태를 나타내는useState와 복잡한 상태를 나타내는 useReducer가 있습니다.

이번 포스팅에서 다룰 내용은 중앙에서 상태를 관리하는 Context APIRedux에 대해서 다루고자 합니다.


Context API

Context API는 React에서 제공하는 내장 API입니다.

Context API 사용법

상태를 관리하는 context를 만들고 상태를 사용하는 범위에 Provider를 씌워줍니다. Provider에 내부에서는 context에 존재하는 상태에 자유롭게 접근이 가능하고 변경이 가능합니다.

useContext를 활용하여 상태를 가져올 수 있습니다. 위에서 언급했듯이 간단한 상태는 useState처럼, 복잡한 상태는 useReducer처럼 가져올 수 있습니다.

// dispatch를 통해 복잡한 상태를 관리 가능
export default function ThemeToggleButton() {
  const { state, dispatch } = useContext(ThemeContext); // 상태 접근

  return (
    <button onClick={() => dispatch({ type: "TOGGLE_THEME" })}>
      현재 테마: {state.theme}
    </button>
  );
}

Context API 주요 특징

Context API를 사용하는 가장 큰 이유는 props drilling을 해결하기 위해서입니다.

props drilling

프로젝트 규모가 커질수록 컴포넌트 계층이 깊어지면서, props를 여러 컴포넌트를 거쳐 하위 컴포넌트로 전달해야 하는 상황이 발생합니다.

이러한 방식은 불필요한 렌더링을 유발하고 코드의 유지보수를 어렵게 만듭니다.

리렌더링의 조건 중에 하나는 props의 변경입니다. 리렌더링을 원하지 않는 중간노드에서 어쩔 수 없이 리렌더링이 일어날 수 있고, useMemo 또는 React.memo로 최적화를 하더라도 코드의 가독성이 떨어질 수 있습니다.

언제 Context API를 사용해야 할까요?

이건 사람마다 다르고 프로젝트에 따라 다릅니다. 하지만, 저의 경우는 props drilling이 3단계 이상으로, 여러 경우 일어나면 사용을 고려합니다.

Context API의 단점

Context API는 완벽하지 않습니다. 그저 props drilling을 해결하기 위해 만들어졌을 뿐입니다. Context API는 여러 단점이 있습니다.

Provider 전체 리렌더링

context 내부의 상태가 변경되면, 그 상태가 존재할 수 있는 Provider내부의 컴포넌트가 전부 리렌더링됩니다. props drilling의 단점 중에 하나인 불필요한 리렌더링이 다시 발생할 수 있는 것입니다. 그러면 최대한 불필요한 리렌더링이 일어나지 않게 context도 많이 만들어서 각각 관리하면 되지 않을까요?

Provider Hell 발생 가능성

context가 많아지고 이에 해당하는 Provider가 많아질 수록 관리가 어려워지고 가독성이 떨어집니다.

<AuthContext.Provider value={authState}>
  <ThemeContext.Provider value={themeState}>
    <CartContext.Provider value={cartState}>
      <UserContext.Provider value={userState}>
        <App />
      </UserContext.Provider>
    </CartContext.Provider>
  </ThemeContext.Provider>
</AuthContext.Provider>

예시코드의 경우 4개의 context일 뿐인데 매우 복잡합니다.


상태관리 라이브러리

위의 경우와 같이 Context API는 규모가 커지게되면 관리하기 불편하고 가독성이 매우 떨어집니다!

상태관리 라이브러리를 사용하게 되면 이러한 문제를 해결할 수 있습니다. 이번 포스팅에서는 상태관리 라이브러리 중 하나인 Redux에 대해 알아보겠습니다.

Flux 패턴

Redux나 zustand와 같은 상태관리 라이브러리는 Flux패턴을 기반으로 만들어졌습니다. Flux패턴은 MVC패턴의 양방향 데이터 흐름의 단점을 개선하기 위해서 만들어졌습니다. 그리고 이러한 구조는 React에 잘 맞았습니다.

양방향 데이터 흐름의 단점

  • 규모가 커질수록 데이터 흐름이 복잡해짐
  • 한 컴포넌트의 상태 변경이 예측하기 어려운 방식으로 다른 컴포넌트에 영향을 줄 수 있음

Flux패턴 구조

Flux단방향 데이터 흐름
Flux는 Action → Dispatcher → Store → View 순서로 동작하며, 데이터가 한 방향으로만 흐르게 됩니다.

작동 방식

  1. 이벤트 발생(사용자의 입력 등)으로 인한 action생성
  2. dispatcheractionstore로 전달
  3. store에서 상태를 변경
  4. 해당 상태를 구독하고 있는 view에서 리렌더링

Redux

ReduxFlux패턴을 기반으로 만들어진 상태관리 라이브러리입니다. 그러면 어떤 것이 변경되었는지 알아보겠습니다.

Redux 작동방식

  1. 이벤트 발생(사용자의 입력 등)으로 인해 컴포넌트는 action객체를 생성
  2. dispatchactionreducer로 전달
    • dispatch(action)이 호출되면, Redux현재 상태(state)actionreducer로 보냅니다.
    • action객체는 typepayload(변경할 데이터)를 포함합니다.
  3. reduceraction을 보고 새로운 상태를 생성
    • reducer는 순수함수로 작성되어야 합니다.
    • reducer는 이전 상태와 action객체를 받아서 새로운 상태 객체 반환
  4. store가 새로운 상태를 업데이트
    • reducer가 반환한 새로운 상태를 store에 저장
  5. 해당 상태를 구독하고 있는 view에서 리렌더링
    • useSelector : store에서 상태를 가져오는 훅
    • useDispatch : action을 발생시키는 훅

Redux 특징

하나의 Store로 구성

Flux패턴의 경우는 여러개의 store로 구성이 되어있지만, Redux는 하나의 중앙 store를 사용합니다.

store의 상태는 불변성을 유지합니다. 이는 상태를 직접 수정하는 것이 불가능하며, 새로운 상태를 반환하는 방식으로 상태를 변경합니다.

Dispatcher가 없음

Flux패턴은 dispatcheractionstore로 전달하지만, Redux의 경우는 dispatch함수 실행을 통해 Redux가 직접 actionstore로 전달합니다.

불변성을 가진 상태

Flux패턴은 store에서 상태를 직접 변경시키지만, Redux의 경우는 상태를 읽기만 가능하고 reducer를 통해서 새로운 상태를 반환하여 업데이트합니다.

Redux 단점

보일러 플레이트 코드가 많음

Redux를 사용할 때는 Action, Reducer, Dispatch, Store 등을 별도로 정의해야 하고, 상태 하나 추가할 때도 많은 파일을 작성해야 하며, 코드가 길어집니다.

Reducer가 불변성을 유지해야 해서 번거로움

상태를 변경시키지 않고 기존 상태와 action을 통해서 새로운 상태를 반환해야하므로 실수가 자주 일어납니다.

RTK(Redux-ToolKit) 사용

RTK를 사용하게 되면 Redux의 문제점을 해결할 수 있습니다.

보일러 플레이트 코드 대폭 감소

createSlice를 사용하면 ActionReducer를 한 곳에서 관리할 수 있어서 보일러 플레이트 코드가 대폭 감소합니다.

Reducer에서 자동으로 상태를 불변하게 선언

createSlice에서는 Immer를 내장하고 있어 직접 상태를 변경해도 자동으로 불변성을 유지해 줍니다.

Immer

불변성을 유지하도록 해주는 JavaScript 라이브러리입니다.

위와 같은 이유로 RTK를 사용하면 조금 더 편리하게 Redux의 기능을 사용할 수 있습니다.


React에서 로컬상태를 관리하는 방법 중 Context API와 Redux에 대해서 정리해보았습니다.

profile
원리를 이해하는 프론트엔드 개발자입니다

0개의 댓글