본문 바로가기

Redux 기초 개념 소개

Redux가 관리하는 상태를 하나의 Store에 두고 action·reducer·selector로 이어지는 단방향 흐름과 현대 Redux Toolkit 경계를 이해합니다.

Redux는 여러 화면이 함께 사용하는 client state를 예측 가능한 규칙으로 관리하는 독립적인 JavaScript 라이브러리입니다.

React에서는 공식 바인딩인 React-Redux와 함께 자주 사용하지만 Redux 자체가 React에 종속되지는 않습니다.

Redux의 핵심은 애플리케이션의 모든 상태를 한곳에 밀어 넣는 것이 아니라, Redux가 관리하기로 정한 상태를 하나의 store와 action 기반 전이 경계에서 다루는 것입니다.

컴포넌트에만 필요한 UI state는 로컬 state로, 주소로 표현할 상태는 URL로, 서버에서 가져온 데이터는 적절한 loader나 cache로 관리할 수 있습니다.

Context와 useReducer도 유용하지만 Context는 Provider 하위에 값을 전달하는 범위이고 reducer는 다음 상태를 계산하는 함수입니다. 두 기능을 함께 쓰면 그 트리에서 state와 dispatch를 공유할 수 있지만, Redux식 외부 store 객체, middleware, DevTools action 기록, selector별 동등성 비교가 자동으로 생기지는 않습니다.

Zustand나 Jotai 같은 외부 store 선택지도 있으므로, Redux는 이름이나 앱 크기보다 필요한 소유권·구독·추적 계약으로 비교해야 합니다.


Redux는 왜 필요한가?

앱 규모만으로 Redux 사용 여부가 정해지지는 않습니다.

다음 신호가 함께 나타나고 action 기반 추적과 개발 도구의 이득이 추가 비용보다 클 때 Redux를 검토합니다.

  • 공유 범위: 같은 client state를 서로 멀리 떨어진 여러 화면과 기능이 읽습니다.
  • 갱신 빈도: 그 상태가 시간에 따라 자주 바뀝니다.
  • 전이 복잡성: 여러 사건이 관련 필드와 불변 조건을 함께 바꿉니다.
  • 협업과 관찰: 변경 이유를 action 단위로 추적하고 동일한 규칙을 팀이 공유할 가치가 큽니다.
  • 확장 지점: 미들웨어, DevTools, 테스트 가능한 reducer 같은 표준 경계가 필요합니다.

Redux는 action을 dispatch하는 공통 입구를 제공하지만 action 이력을 자동으로 영구 저장하지는 않습니다.

Redux DevTools 연동이 활성화되고 Extension 같은 모니터가 연결됐거나 로깅 미들웨어를 구성했으며 state와 action을 관찰 가능한 직렬화 형태로 유지했을 때 action과 전후 state를 디버깅 증거로 확인할 수 있습니다.


Redux의 세 가지 핵심 원칙

단일 진실 공급원 (Single Source of Truth)

일반적인 Redux 앱은 하나의 store를 사용하며, 그 store는 Redux가 관리하는 전체 state tree를 보관합니다.

이는 모든 컴포넌트 로컬 state, URL state, 서버 cache까지 Redux에 넣으라는 뜻이 아닙니다.

상태는 읽기 전용 (State is Read-only)

store state를 직접 쓰지 않고 action을 dispatch해 변경 의도를 전달합니다.

action은 type 필드를 가진 평범한 객체이며, 필요한 경우 payload로 사건의 데이터를 담습니다.

변경은 순수 reducer로 계산 (Changes are Made with Pure Functions)

reducer는 이전 state와 action을 받아 다음 state를 동기적으로 계산합니다.

같은 입력에 같은 결과를 내고, 이전 state를 직접 바꾸거나 API 호출·타이머·랜덤 값 생성 같은 부수 효과를 실행하지 않아야 합니다.

Redux Toolkit의 createSlice에서는 Immer 덕분에 변경처럼 보이는 문법을 쓸 수 있지만, 이전 state 자체를 바꾸지 않는 불변 갱신 계약은 유지됩니다.


Redux의 핵심 구성 요소

Store

Redux가 관리하는 현재 state tree를 보관합니다. dispatch(action)으로 업데이트를 요청하고, getState()로 현재 snapshot을 읽으며, subscribe(listener)로 업데이트 알림을 받을 수 있습니다.

일반적인 앱은 하나의 store를 사용하고 새 코드는 Redux Toolkit의 configureStore로 설정합니다.

Action

애플리케이션에서 무슨 일이 있었는지를 설명하는 평범한 객체입니다.

type은 사건 종류를 식별하고 payload는 reducer가 전이를 계산하는 데 필요한 최소 데이터를 담습니다. action 자체가 state를 변경하지는 않습니다.

Reducer

reducer(previousState, action) 형태의 순수 함수입니다.

현재 state와 action으로 다음 state를 계산하며 비동기 작업과 부수 효과는 reducer 밖에 둡니다.

Dispatch

action을 store에 전달하는 함수입니다.

store는 root reducer를 실행해 다음 state를 저장한 뒤 구독자에게 업데이트를 알립니다. 미들웨어는 dispatch 경계에서 로깅이나 비동기 로직 같은 기능을 추가할 수 있습니다.

Selector와 subscriber

selector는 store state에서 컴포넌트가 필요한 값이나 파생 값을 읽습니다.

React-Redux의 useSelector는 store 업데이트 뒤 selector를 다시 실행하고, 기본적으로 이전 선택 결과와 새 결과를 ===로 비교합니다. Redux 업데이트로 인한 컴포넌트 렌더는 선택 결과가 달라질 때 발생하지만, 부모 렌더 같은 별도 경로는 여전히 존재합니다.


Redux의 데이터 흐름 (단방향)

Redux는 “무슨 일이 있었는가”와 “그 사건으로 상태가 어떻게 바뀌는가”를 한 방향으로 연결합니다.

Redux의 단방향 상태 전이와 관찰 경계 — 상태 추적

동기 plain action 뒤 root state와 useSelector 선택 결과가 달라질 수도 같을 수도 있다. store snapshot/선택 참조/구독 렌더 요청을 추적하고 별도 부모 렌더는 남긴다.

Redux는 action에서 snapshot과 선택 결과로 이어집니다

REACT STATE · OWNERSHIP & TRANSITION

Redux는 action에서 snapshot과 선택 결과로 이어집니다

동기 action을 dispatch하면 root reducer가 다음 state를 계산합니다. React-Redux는 선택 결과를 비교하며, 별도의 부모 렌더 경로도 존재합니다.

동기 Redux action에서 선택 결과 비교까지동기 plain action을 받은 Redux store가 root reducer로 다음 state를 저장하고 구독자를 알립니다. useSelector의 기본 === 비교에서 선택 결과가 달라진 이 구독 경로는 렌더를 요청하고 같으면 이 경로의 요청을 건너뜁니다. 별도 부모 렌더 경로는 남아 있으므로 모든 렌더 여부를 store 구독 하나로 결정하지 않습니다.Redux storeUI 사건동기 plain actiondispatch(action)store에 action 전달순수 root reducer현재 state → 다음 stateStore snapshotgetState() · 구독 알림useSelector 결과기본 === 비교선택 결과가===로 달라졌는가?새 값으로 UI 렌더이 Redux 구독 경로같은 선택 결과이 구독 갱신으로는렌더 요청 없음부모 렌더 등별도 경로는 남음예아니요
동기 Redux action에서 선택 결과 비교까지동기 plain action을 받은 Redux store가 root reducer로 다음 state를 저장하고 구독자를 알립니다. useSelector의 기본 === 비교에서 선택 결과가 달라진 이 구독 경로는 렌더를 요청하고 같으면 이 경로의 요청을 건너뜁니다. 별도 부모 렌더 경로는 남아 있으므로 모든 렌더 여부를 store 구독 하나로 결정하지 않습니다.Redux storeUI 사건동기 plain actiondispatch(action)store에 action 전달순수 root reducer현재 state→ 다음 stateStore snapshotgetState()구독 알림useSelector 결과기본 === 비교선택 결과가===로달라졌는가?새 값으로 UI 렌더이 Redux 구독 경로같은 선택 결과이 구독 갱신으로는렌더 요청 없음부모 렌더 등별도 경로는 남음예아니요

순서를 읽는 예 · count를 1 늘리는 reducer

이전 store snapshot: count = 0
dispatch({ type: "incremented" })
getState().count → 1
useSelector(s => s.count)
→ 선택 결과 변경

count를 1 늘리는 reducer를 가정한 설명용 예입니다. 동기 plain action 기준이며 비동기 작업과 부수효과는 reducer 밖에서 처리합니다.

동기 Redux 업데이트 한 바퀴와 각 단계의 관찰 지점
단계 책임 확인할 계약
1 · UI 사건 사용자 입력이나 비동기 작업 완료가 상태 변경 의도를 만듭니다. 사건을 먼저 설명하고 다음 값을 UI에서 직접 쓰지 않습니다.
2 · Action / dispatch dispatch(action)으로 type과 필요한 payload를 store에 보냅니다. action은 “무슨 일이 있었는가”를 기록하는 평범한 객체입니다.
3 · Reducer 현재 state와 action만으로 다음 state를 동기적으로 계산합니다. 이전 state를 바꾸지 않고 API·타이머·랜덤 같은 부수 효과를 실행하지 않습니다.
4 · Store snapshot root reducer의 반환값을 현재 state로 저장하고 구독자에게 업데이트를 알립니다. getState()는 이 dispatch가 끝난 뒤의 snapshot을 반환합니다.
5 · Selector / subscriber selector가 필요한 조각이나 파생 값을 읽고 이전 선택 결과와 비교합니다. useSelector는 기본적으로 선택 결과의 === 참조 동일성을 사용합니다.
6 · UI render Redux 업데이트로 선택 결과가 달라진 컴포넌트가 새 값을 화면에 반영합니다. 부모 렌더 같은 별도 경로는 남으므로 Redux 구독만으로 모든 렌더가 결정되지는 않습니다.
  1. UI 사건

    사용자 입력이나 비동기 완료가 변경 의도를 만듭니다.

  2. Action을 dispatch

    type과 필요한 payload로 “무슨 일이 있었는가”를 store에 보냅니다.

  3. Reducer 계산

    현재 state와 action만으로 순수하고 불변인 다음 state를 만듭니다.

  4. Store snapshot

    root reducer 반환값을 저장하고 구독자에게 업데이트를 알립니다. 동기 dispatch 뒤 getState()는 새 snapshot을 읽습니다.

  5. Selector 확인

    useSelector가 선택 결과를 다시 계산하고 기본 === 비교를 수행합니다.

  6. UI 반영

    선택 결과가 달라진 Redux 구독 경로가 새 화면을 렌더합니다. 별도 부모 렌더 경로는 남습니다.

Single store

Redux가 관리하는 state tree

일반적인 Redux 앱은 store 하나를 사용합니다. 컴포넌트 로컬 state, URL, 서버 캐시까지 모두 넣어야 한다는 뜻은 아닙니다.

Read only

변경 입구는 dispatch

store state를 직접 쓰지 않고 action을 dispatch합니다. 이 경계가 action 기반 관찰과 미들웨어 확장을 가능하게 합니다.

Pure reducer

전이는 순수하고 불변

같은 state와 action에는 같은 결과를 내고 부수 효과는 밖에 둡니다. Redux Toolkit의 mutation처럼 보이는 문법도 이전 state 자체를 바꾸는 계약은 아닙니다.

action 이력은 Redux가 자동으로 영구 보관하는 데이터가 아닙니다. DevTools 연동과 모니터 또는 로깅을 구성하고 state·action을 관찰 가능한 직렬화 형태로 유지했을 때 전후 값을 디버깅 증거로 볼 수 있습니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
원도표의 전체 관계·조건·설명
구분내용
전체 관계·조건 설명동기 plain action을 받은 Redux store가 root reducer로 다음 state를 저장하고 구독자에게 알립니다. useSelector의 기본 === 비교에서 선택 결과가 달라진 이 구독 경로는 렌더를 요청하고 같으면 이 경로의 요청을 건너뜁니다. 별도 부모 렌더 경로는 남아 있으므로 모든 렌더 여부를 store 구독 하나로 결정하지 않습니다.
원도표 표 행단계 | 책임 | 확인할 계약
원도표 표 행1 · UI 사건 | 사용자 입력이나 비동기 작업 완료가 상태 변경 의도를 만듭니다. | 사건을 먼저 설명하고 다음 값을 UI에서 직접 쓰지 않습니다.
원도표 표 행2 · Action / dispatch | dispatch(action)으로 type과 필요한 payload를 store에 보냅니다. | action은 “무슨 일이 있었는가”를 기록하는 평범한 객체입니다.
원도표 표 행3 · Reducer | 현재 state와 action만으로 다음 state를 동기적으로 계산합니다. | 이전 state를 바꾸지 않고 API·타이머·랜덤 같은 부수 효과를 실행하지 않습니다.
원도표 표 행4 · Store snapshot | root reducer의 반환값을 현재 state로 저장하고 구독자에게 업데이트를 알립니다. | getState()는 이 dispatch가 끝난 뒤의 snapshot을 반환합니다.
원도표 표 행5 · Selector / subscriber | selector가 필요한 조각이나 파생 값을 읽고 이전 선택 결과와 비교합니다. | useSelector는 기본적으로 선택 결과의 === 참조 동일성을 사용합니다.
원도표 표 행6 · UI render | Redux 업데이트로 선택 결과가 달라진 컴포넌트가 새 값을 화면에 반영합니다. | 부모 렌더 같은 별도 경로는 남으므로 Redux 구독만으로 모든 렌더가 결정되지는 않습니다.
원도표 조건·주석UI 사건: 사용자 입력이나 비동기 완료가 변경 의도를 만듭니다.
원도표 설명type과 필요한 payload로 “무슨 일이 있었는가”를 store에 보냅니다.
원도표 조건·주석Action을 dispatch: type과 필요한 payload로 “무슨 일이 있었는가”를 store에 보냅니다.
원도표 조건·주석Reducer 계산: 현재 state와 action만으로 순수하고 불변인 다음 state를 만듭니다.
원도표 조건·주석Store snapshot: root reducer 반환값을 저장하고 구독자에게 업데이트를 알립니다. 동기 dispatch 뒤 getState()는 새 snapshot을 읽습니다.
원도표 설명useSelector가 선택 결과를 다시 계산하고 기본 === 비교를 수행합니다.
원도표 조건·주석Selector 확인: useSelector가 선택 결과를 다시 계산하고 기본 === 비교를 수행합니다.
원도표 조건·주석UI 반영: 선택 결과가 달라진 Redux 구독 경로가 새 화면을 렌더합니다. 별도 부모 렌더 경로는 남습니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
plain actionstore.count: 0 / action: incrementedUI가 동기 plain action을 store에 dispatch한다middleware 확장 전체를 이 그림의 필수 단계로 넣지 않는다
next snapshotstore.count: 1 / selector: 다시 계산root reducer의 결과를 store가 저장하고 구독자에게 알린다여러 selector의 결과가 모두 달라질 필요는 없다
선택 비교선택 다름: 이 구독 렌더 요청 / 선택 같음: 이 요청 없음기본 === 비교에서 선택 결과가 다를 때만 이 구독의 렌더를 요청한다별도 부모 렌더까지 없다고 판정하지 않는다

Redux의 단방향 상태 전이와 관찰 경계 — 상태 추적

동기 plain action 뒤 root state와 useSelector 선택 결과가 달라질 수도 같을 수도 있다. store snapshot/선택 참조/구독 렌더 요청을 추적하고 별도 부모 렌더는 남긴다.

Redux는 action에서 snapshot과 선택 결과로 이어집니다

REACT STATE · OWNERSHIP & TRANSITION

Redux는 action에서 snapshot과 선택 결과로 이어집니다

동기 action을 dispatch하면 root reducer가 다음 state를 계산합니다. React-Redux는 선택 결과를 비교하며, 별도의 부모 렌더 경로도 존재합니다.

동기 Redux action에서 선택 결과 비교까지동기 plain action을 받은 Redux store가 root reducer로 다음 state를 저장하고 구독자를 알립니다. useSelector의 기본 === 비교에서 선택 결과가 달라진 이 구독 경로는 렌더를 요청하고 같으면 이 경로의 요청을 건너뜁니다. 별도 부모 렌더 경로는 남아 있으므로 모든 렌더 여부를 store 구독 하나로 결정하지 않습니다.Redux storeUI 사건동기 plain actiondispatch(action)store에 action 전달순수 root reducer현재 state → 다음 stateStore snapshotgetState() · 구독 알림useSelector 결과기본 === 비교선택 결과가===로 달라졌는가?새 값으로 UI 렌더이 Redux 구독 경로같은 선택 결과이 구독 갱신으로는렌더 요청 없음부모 렌더 등별도 경로는 남음예아니요
동기 Redux action에서 선택 결과 비교까지동기 plain action을 받은 Redux store가 root reducer로 다음 state를 저장하고 구독자를 알립니다. useSelector의 기본 === 비교에서 선택 결과가 달라진 이 구독 경로는 렌더를 요청하고 같으면 이 경로의 요청을 건너뜁니다. 별도 부모 렌더 경로는 남아 있으므로 모든 렌더 여부를 store 구독 하나로 결정하지 않습니다.Redux storeUI 사건동기 plain actiondispatch(action)store에 action 전달순수 root reducer현재 state→ 다음 stateStore snapshotgetState()구독 알림useSelector 결과기본 === 비교선택 결과가===로달라졌는가?새 값으로 UI 렌더이 Redux 구독 경로같은 선택 결과이 구독 갱신으로는렌더 요청 없음부모 렌더 등별도 경로는 남음예아니요

순서를 읽는 예 · count를 1 늘리는 reducer

이전 store snapshot: count = 0
dispatch({ type: "incremented" })
getState().count → 1
useSelector(s => s.count)
→ 선택 결과 변경

count를 1 늘리는 reducer를 가정한 설명용 예입니다. 동기 plain action 기준이며 비동기 작업과 부수효과는 reducer 밖에서 처리합니다.

동기 Redux 업데이트 한 바퀴와 각 단계의 관찰 지점
단계 책임 확인할 계약
1 · UI 사건 사용자 입력이나 비동기 작업 완료가 상태 변경 의도를 만듭니다. 사건을 먼저 설명하고 다음 값을 UI에서 직접 쓰지 않습니다.
2 · Action / dispatch dispatch(action)으로 type과 필요한 payload를 store에 보냅니다. action은 “무슨 일이 있었는가”를 기록하는 평범한 객체입니다.
3 · Reducer 현재 state와 action만으로 다음 state를 동기적으로 계산합니다. 이전 state를 바꾸지 않고 API·타이머·랜덤 같은 부수 효과를 실행하지 않습니다.
4 · Store snapshot root reducer의 반환값을 현재 state로 저장하고 구독자에게 업데이트를 알립니다. getState()는 이 dispatch가 끝난 뒤의 snapshot을 반환합니다.
5 · Selector / subscriber selector가 필요한 조각이나 파생 값을 읽고 이전 선택 결과와 비교합니다. useSelector는 기본적으로 선택 결과의 === 참조 동일성을 사용합니다.
6 · UI render Redux 업데이트로 선택 결과가 달라진 컴포넌트가 새 값을 화면에 반영합니다. 부모 렌더 같은 별도 경로는 남으므로 Redux 구독만으로 모든 렌더가 결정되지는 않습니다.
  1. UI 사건

    사용자 입력이나 비동기 완료가 변경 의도를 만듭니다.

  2. Action을 dispatch

    type과 필요한 payload로 “무슨 일이 있었는가”를 store에 보냅니다.

  3. Reducer 계산

    현재 state와 action만으로 순수하고 불변인 다음 state를 만듭니다.

  4. Store snapshot

    root reducer 반환값을 저장하고 구독자에게 업데이트를 알립니다. 동기 dispatch 뒤 getState()는 새 snapshot을 읽습니다.

  5. Selector 확인

    useSelector가 선택 결과를 다시 계산하고 기본 === 비교를 수행합니다.

  6. UI 반영

    선택 결과가 달라진 Redux 구독 경로가 새 화면을 렌더합니다. 별도 부모 렌더 경로는 남습니다.

Single store

Redux가 관리하는 state tree

일반적인 Redux 앱은 store 하나를 사용합니다. 컴포넌트 로컬 state, URL, 서버 캐시까지 모두 넣어야 한다는 뜻은 아닙니다.

Read only

변경 입구는 dispatch

store state를 직접 쓰지 않고 action을 dispatch합니다. 이 경계가 action 기반 관찰과 미들웨어 확장을 가능하게 합니다.

Pure reducer

전이는 순수하고 불변

같은 state와 action에는 같은 결과를 내고 부수 효과는 밖에 둡니다. Redux Toolkit의 mutation처럼 보이는 문법도 이전 state 자체를 바꾸는 계약은 아닙니다.

action 이력은 Redux가 자동으로 영구 보관하는 데이터가 아닙니다. DevTools 연동과 모니터 또는 로깅을 구성하고 state·action을 관찰 가능한 직렬화 형태로 유지했을 때 전후 값을 디버깅 증거로 볼 수 있습니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.

관계도의 보충 설명

원도표의 전체 관계·조건·설명
구분내용
전체 관계·조건 설명동기 plain action을 받은 Redux store가 root reducer로 다음 state를 저장하고 구독자에게 알립니다. useSelector의 기본 === 비교에서 선택 결과가 달라진 이 구독 경로는 렌더를 요청하고 같으면 이 경로의 요청을 건너뜁니다. 별도 부모 렌더 경로는 남아 있으므로 모든 렌더 여부를 store 구독 하나로 결정하지 않습니다.
원도표 표 행단계 | 책임 | 확인할 계약
원도표 표 행1 · UI 사건 | 사용자 입력이나 비동기 작업 완료가 상태 변경 의도를 만듭니다. | 사건을 먼저 설명하고 다음 값을 UI에서 직접 쓰지 않습니다.
원도표 표 행2 · Action / dispatch | dispatch(action)으로 type과 필요한 payload를 store에 보냅니다. | action은 “무슨 일이 있었는가”를 기록하는 평범한 객체입니다.
원도표 표 행3 · Reducer | 현재 state와 action만으로 다음 state를 동기적으로 계산합니다. | 이전 state를 바꾸지 않고 API·타이머·랜덤 같은 부수 효과를 실행하지 않습니다.
원도표 표 행4 · Store snapshot | root reducer의 반환값을 현재 state로 저장하고 구독자에게 업데이트를 알립니다. | getState()는 이 dispatch가 끝난 뒤의 snapshot을 반환합니다.
원도표 표 행5 · Selector / subscriber | selector가 필요한 조각이나 파생 값을 읽고 이전 선택 결과와 비교합니다. | useSelector는 기본적으로 선택 결과의 === 참조 동일성을 사용합니다.
원도표 표 행6 · UI render | Redux 업데이트로 선택 결과가 달라진 컴포넌트가 새 값을 화면에 반영합니다. | 부모 렌더 같은 별도 경로는 남으므로 Redux 구독만으로 모든 렌더가 결정되지는 않습니다.
원도표 조건·주석UI 사건: 사용자 입력이나 비동기 완료가 변경 의도를 만듭니다.
원도표 설명type과 필요한 payload로 “무슨 일이 있었는가”를 store에 보냅니다.
원도표 조건·주석Action을 dispatch: type과 필요한 payload로 “무슨 일이 있었는가”를 store에 보냅니다.
원도표 조건·주석Reducer 계산: 현재 state와 action만으로 순수하고 불변인 다음 state를 만듭니다.
원도표 조건·주석Store snapshot: root reducer 반환값을 저장하고 구독자에게 업데이트를 알립니다. 동기 dispatch 뒤 getState()는 새 snapshot을 읽습니다.
원도표 설명useSelector가 선택 결과를 다시 계산하고 기본 === 비교를 수행합니다.
원도표 조건·주석Selector 확인: useSelector가 선택 결과를 다시 계산하고 기본 === 비교를 수행합니다.
원도표 조건·주석UI 반영: 선택 결과가 달라진 Redux 구독 경로가 새 화면을 렌더합니다. 별도 부모 렌더 경로는 남습니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
plain actionstore.count: 0 / action: incrementedUI가 동기 plain action을 store에 dispatch한다middleware 확장 전체를 이 그림의 필수 단계로 넣지 않는다
next snapshotstore.count: 1 / selector: 다시 계산root reducer의 결과를 store가 저장하고 구독자에게 알린다여러 selector의 결과가 모두 달라질 필요는 없다
선택 비교선택 다름: 이 구독 렌더 요청 / 선택 같음: 이 요청 없음기본 === 비교에서 선택 결과가 다를 때만 이 구독의 렌더를 요청한다별도 부모 렌더까지 없다고 판정하지 않는다

UI 사건 또는 비동기 완료: 사용자 입력이나 middleware의 비동기 작업 완료가 변경 의도를 만듭니다.

Action dispatch: 컴포넌트나 middleware가 action을 store에 전달합니다.

Reducer 실행: store가 현재 state와 action으로 root reducer를 실행합니다.

새 snapshot 저장: reducer 반환값이 store의 현재 state가 되고 구독자에게 업데이트가 알려집니다.

Selector 비교: React-Redux 구독자는 필요한 값을 다시 선택하고 이전 선택 결과와 비교합니다.

UI 반영: 해당 Redux 구독 경로에서 선택 결과가 바뀌었다면 컴포넌트가 새 값으로 렌더됩니다.

비동기 작업과 부수 효과는 reducer 밖에서 수행한 뒤 필요한 action을 dispatch합니다.


Redux Toolkit과의 관계

Redux Toolkit은 현대 Redux 로직을 작성하는 공식 권장 경로입니다.

  • configureStore: root reducer, 표준 middleware, 기본 활성화된 Redux DevTools 연동을 포함한 권장 store 설정을 제공하며 devTools 옵션으로 연동을 제어할 수 있습니다.
  • createSlice: action type·creator와 case reducer를 함께 만들고 Immer 기반 불변 갱신을 간결하게 작성하게 합니다.
  • createAsyncThunk: 사용자 정의 Promise 작업에 대해 pending, fulfilled, rejected 생명주기 action을 만들고 condition과 AbortSignal 기반 취소 지점을 제공합니다. 재시도·자동 중복 제거·cache 정책까지 제공하는 일반 서버 데이터 해법은 아닙니다.
  • RTK Query: Redux 앱에서 client data fetching과 cache를 관리할 때 공식 문서가 기본 접근으로 가르치는 도구입니다. 요청 상태와 cache 무효화를 제공하며, 서버 렌더나 route loader가 소유하는 데이터는 해당 프레임워크 경계를 따릅니다.
  • React-Redux: useSelector, useDispatch 등으로 React 컴포넌트를 store에 연결하는 별도 공식 패키지입니다.
Redux Toolkit API는 각기 다른 책임을 맡습니다

REACT STATE · OWNERSHIP & TRANSITION

Redux Toolkit API는 각기 다른 책임을 맡습니다

store 설정, action·reducer 생성, 사용자 정의 비동기 작업, 서버 캐시와 React 구독을 구분합니다. 어떤 상태를 Redux가 소유할지는 먼저 결정해야 합니다.

비동기 작업과 서버 캐시를 구분

서버 응답 · 요청 상태 · 캐시
→ RTK Query
사용자 정의 Promise 작업
→ createAsyncThunk

createAsyncThunk의 생명주기 action만으로 재시도·자동 중복 제거·캐시 정책이 완성되지는 않습니다. 서버 렌더·route loader의 소유권도 구분합니다.

Redux 역할과 현재 권장 API의 책임 경계
역할 권장 API · 제공하는 것 직접 결정할 것
Store
configureStore
root reducer 결합, 표준 미들웨어와 기본 활성화된 Redux DevTools 연동을 포함한 권장 설정; devTools 옵션으로 제어
어떤 client state를 Redux가 소유할지와 slice 경계
Action + reducer
createSlice
action type·creator와 case reducer 생성, Immer 기반의 불변 갱신 작성 경로
action 어휘, state shape, 순수한 전이 규칙과 불변 조건
Custom async
createAsyncThunk
Promise 생명주기 action과 condition·AbortSignal 취소 지점 제공
취소 시점·재시도·자동 중복 제거·캐시 정책; 일반 서버 캐시의 기본 해법은 아님
Server data
RTK Query
Redux 앱의 client data fetching과 캐시, 요청 상태, 무효화 도구
서버 렌더·라우트 로더의 데이터 경계와 API의 권한·일관성 계약
React binding
useSelector
React-Redux가 store를 구독하고 selector 결과를 컴포넌트에 연결
선택 범위, memoization, 참조 안정성과 별도 부모 렌더 비용

Store · configureStore

권장 store 설정과 표준 미들웨어·기본 활성화된 DevTools 연동을 제공합니다. 연동 옵션과 Redux가 소유할 state 범위는 직접 정합니다.

Action + reducer · createSlice

action과 case reducer를 함께 만들고 Immer로 불변 갱신을 간결하게 씁니다. action 어휘와 전이 규칙은 설계 책임입니다.

Custom async · createAsyncThunk

Promise 생명주기 action과 condition·AbortSignal을 제공합니다. 취소 시점·재시도·중복 제거·캐시 정책은 직접 정합니다.

Server data · RTK Query

Redux 앱의 client data fetching과 캐시에 권장되는 기본 경로입니다. 요청 상태와 캐시 무효화를 제공하며, 서버 렌더·라우트 데이터의 소유권과 API 권한·일관성 계약은 별도로 정합니다.

React binding · useSelector

React-Redux가 selector 결과를 화면에 연결합니다. 선택 범위와 참조 안정성, 부모 렌더는 별도로 판단합니다.

Redux signal

공유·빈도·복잡성·추적 가치

많은 화면이 같은 client state를 읽고, 갱신이 잦고, 전이 규칙이 복잡하며, 팀이 action 기반 추적과 도구의 이득을 얻을 때 검토합니다.

Keep the boundary

규모만으로 자동 선택하지 않는다

로컬 UI state는 컴포넌트에, 주소 상태는 URL에 둘 수 있습니다. Context+reducer는 Provider 트리의 공유 도구이며 외부 store·middleware·DevTools·selector 비교를 자동 제공하지 않습니다.

createStore는 학습과 기존 코드에서 볼 수 있지만, 새 Redux 로직은 공식 권장인 Redux Toolkit의 configureStore에서 시작합니다.

Store에 맡길 전이를 JavaScript로 먼저 계산하기

Redux를 연결하기 전에 순수 reducer 한 개의 계약을 읽어 보겠습니다. 다음은 장바구니 수량만 관리하는 작은 계산 예이며 Redux store나 React 구독을 직접 실행하는 코드는 아닙니다. cart/itemAdded의 quantity는 양의 정수로 이미 검증되어 들어온다고 가정합니다.

function cartReducer(state = { count: 0 }, action) {
  if (action.type === 'cart/itemAdded') {
    return { ...state, count: state.count + action.quantity };
  }
  return state;
}

const before = { count: 2 };
const after = cartReducer(before, {
  type: 'cart/itemAdded',
  quantity: 3,
});

console.log(before.count, after.count); // 2 5
console.log(before === after);         // false
console.log(cartReducer(after, { type: 'theme/changed' }) === after); // true

마지막 action은 이 reducer의 관심사가 아니므로 원 상태를 그대로 반환합니다. 여러 slice reducer가 같은 action을 받을 수 있는 Redux에서는 모르는 action에도 기존 state를 반환하고, undefined state에는 초기값을 반환하는 계약이 필요합니다. Redux의 초기화 설명에 따르면 store 생성 때 초기화 action도 reducer를 통과합니다. 앞 절의 React 카운터에서 선택한 “모르는 action이면 오류” 정책을 그대로 Redux root reducer에 복사하면 안 되는 이유입니다.

그 뒤 store가 새 snapshot을 저장했다고 생각하고 selector를 추적합니다. state => state.count의 이전 결과 2와 다음 결과 5는 다르므로 이 값을 구독하는 UI에 변경이 전달됩니다. action 문자열이 UI를 직접 고치는 것이 아니라, 계산 결과를 다시 선택한 값이 달라졌기 때문입니다.

상태 변경과 선택 결과 변경을 구별하기

store의 이전 state는 { count: 5, theme: 'light' }, 다음 state는 { count: 5, theme: 'dark' }라고 가정합니다. 두 컴포넌트의 selector는 각각 state => state.count와 state => ({ count: state.count })이고, 기본 === 비교를 사용합니다. 각 선택 결과는 같은 것으로 비교될까요? 이를 근거로 “두 컴포넌트는 어떤 경로로도 다시 렌더되지 않는다”라고 결론 낼 수도 있을까요?

숫자와 새 객체의 비교를 나누는 풀이

첫 selector는 이전과 다음 모두 숫자 5이므로 5 === 5가 참입니다. 이 Redux 구독 갱신 자체로 새 렌더를 요구하지 않습니다. 두 번째는 호출마다 새 객체를 만들어 내용이 같은 { count: 5 }끼리도 ===가 거짓이므로 이 구독 경로에서 갱신됩니다. 필요한 값을 각각 선택하거나 memoized selector·명시적인 비교 함수를 검토할 수 있지만, 먼저 불필요한 새 객체가 원인인지 확인합니다.

두 결과 모두 부모 렌더 같은 별도 경로를 없애지는 않습니다. React-Redux의 equality 설명은 기본 참조 비교와 새 객체 반환을 구별합니다. 위 계산은 이 계약의 판단 연습이지 실제 React-Redux의 렌더 횟수를 측정한 기록은 아닙니다.


이 장에서는 Redux가 관리하는 state의 단일 store, dispatch를 통한 읽기 전용 변경 경계, 순수 reducer, selector 기반 구독을 살펴보았습니다.

Redux는 규모가 큰 앱의 자동 정답이 아니며 local UI state, URL, server cache와 Redux client state의 소유권을 먼저 나눠야 합니다.

다음 절에서는 useReducer와 Context를 결합한 구조를 살펴보고, 컴포넌트 state owner·reducer 계산 책임·Provider 공급 범위와 외부 store의 차이를 비교합니다.