• ABOUT
  • PORTFOLIO
  • POSTS
  • GUESTBOOK

ยฉ 2025 BlueCool12 All rights reserved.

2025.07.28React

๐Ÿ—ƒ๏ธ Redux ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ธฐ - Store, Action, Reducer, Dispatch

1. Redux๋ž€?

Redux๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์˜คํ”ˆ ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค. ์ฃผ๋กœ ๋ฆฌ์•กํŠธ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜์ง€๋งŒ ๋ฆฌ์•กํŠธ์— ์˜์กดํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ๋‚˜ ๋ฐ”๋‹๋ผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฆฌ์•กํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ทœ๋ชจ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ์ด ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋กœ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„์— ๊ฑธ์ณ props๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋Š” ํ˜„์ƒ์„ Props Drilling์ด๋ผ๊ณ  ํ•œ๋‹ค.

Redux๋Š” ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ „์—ญ ์ƒํƒœ๋ฅผ ํ•˜๋‚˜์˜ ์ €์žฅ์†Œ์—์„œ ๊ด€๋ฆฌํ•˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ๋‹ค.

[Redux์˜ 3๋Œ€ ์›์น™]

  • ๋‹จ์ผ ์ถœ์ฒ˜์˜ ์ง„์‹ค (Single Source of Truth)
    ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋“  ์ƒํƒœ๋Š” ํ•˜๋‚˜์˜ ์ €์žฅ์†Œ(Store)์— ํ•˜๋‚˜์˜ ๊ฐ์ฒด ํŠธ๋ฆฌ ํ˜•ํƒœ๋กœ ์ €์žฅ๋œ๋‹ค.
  • ์ƒํƒœ๋Š” ์ฝ๊ธฐ ์ „์šฉ (State is Read-Only)
    ์ƒํƒœ๋ฅผ ์ง์ ‘ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ฐ˜๋“œ์‹œ ์•ก์…˜(Action) ๊ฐ์ฒด๋ฅผ ๋””์ŠคํŒจ์น˜(Dispatch)ํ•ด์•ผ ํ•œ๋‹ค.
  • ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋ฅผ ํ†ตํ•œ ๋ณ€ํ™” (Changes are made with Pure Functions)
    ์ƒํƒœ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋ณ€๊ฒฝ๋˜๋Š”์ง€๋ฅผ ์ •์˜ํ•˜๊ธฐ ์œ„ํ•ด ๋ฆฌ๋“€์„œ(Reducer)๋ผ๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•˜๋ฉฐ ๋ฆฌ๋“€์„œ๋Š” ์ด์ „ ์ƒํƒœ์™€ ์•ก์…˜์„ ๋ฐ›์•„ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.


2. Redux์˜ ํ•ต์‹ฌ ๊ตฌ์„ฑ์š”์†Œ 

- Action

Action์€ ์ƒํƒœ ๋ณ€ํ™”๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋Š” ์‚ฌ์‹ค์„ ์„ค๋ช…ํ•˜๋Š” ๊ฐ์ฒด์ด๋‹ค. ๋ฆฌ๋•์Šค์—์„œ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ฐ˜๋“œ์‹œ Action ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋ฉฐ Action์€ ํ•ญ์ƒ type์ด๋ผ๋Š” ํ•„์ˆ˜ ์†์„ฑ์„ ๊ฐ€์ง„๋‹ค. ๋˜ํ•œ ํ•„์š”์— ๋”ฐ๋ผ payload์™€ ๊ฐ™์€ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋‹ค.

const incrementAction = {
type: 'INCREMENT',
payload: 1
};

์ด Action ๊ฐ์ฒด๋Š” dispatch ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋˜๋ฉฐ ๋ฆฌ๋•์Šค์˜ ์ƒํƒœ ๋ณ€๊ฒฝ ๊ณผ์ •์— ์‚ฌ์šฉ๋œ๋‹ค.


- Dispatch

Dispatch๋Š” Action์„ store์— ์ „๋‹ฌํ•˜๋Š” ํ•จ์ˆ˜๋‹ค. ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ dispatch()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด Action์ด store๋กœ ์ „๋‹ฌ๋˜๊ณ  reducer๊ฐ€ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜์—ฌ ์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ๋œ๋‹ค.

๋ฆฌ์•กํŠธ์—์„œ ๋ฆฌ๋•์Šค๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” react-redux ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ useDispatch์™€ useSelector ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

import { useDispatch, useSelector } from "react-redux";

const Counter = () => {
const dispatch = useDispatch();
const count = useSelector((state) => state.counter.value);

const incrementAction = {
type: 'INCREMENT',
payload: 1
};

const handleClick = () => {
dispatch(incrementAction);
};

return (
<div>
<p>์นด์šดํŠธ: {count}</p>
<button onClick={handleClick}>+1 ์ฆ๊ฐ€</button>
</div>
);
};

export default Counter;

useDispatch๋Š” ๋ฆฌ๋•์Šค store์— ์•ก์…˜์„ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•œ dispatch ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ useSelector๋Š” store์— ์ €์žฅ๋œ ์ƒํƒœ๋ฅผ ์กฐํšŒํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.

๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด handleClick ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๊ณ  dispatch(incrementAction)์„ ํ†ตํ•ด ์•ก์…˜์ด store๋กœ ์ „๋‹ฌ๋œ๋‹ค. ์ดํ›„ reducer๊ฐ€ ์•ก์…˜์˜ type์„ ํ™•์ธํ•˜์—ฌ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ  ๋ณ€๊ฒฝ๋œ ์ƒํƒœ๊ฐ€ ๋‹ค์‹œ ์ปดํฌ๋„ŒํŠธ์— ๋ฐ˜์˜๋œ๋‹ค.


- Reducer

Reducer๋Š” ํ˜„์žฌ ์ƒํƒœ(state)์™€ ์•ก์…˜์„ ๋ฐ›์•„ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜์ด๋‹ค. ๋ฆฌ๋•์Šค์—์„œ๋Š” ์ƒํƒœ๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  ํ•ญ์ƒ ์ƒˆ๋กœ์šด ์ƒํƒœ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๊ด€๋ฆฌํ•œ๋‹ค.

Reducer๋Š” ์•ก์…˜์˜ type์— ๋”ฐ๋ผ ์ƒํƒœ๋ฅผ ์–ด๋–ป๊ฒŒ ๋ณ€๊ฒฝํ• ์ง€ ์ •์˜ํ•œ๋‹ค.

const counterReducer = (state = { value: 0 }, action) => {
switch (action.type) {
case 'INCREMENT':
return { value: state.value + action.payload };

default:
return state;
}
};

INCREMENT ์•ก์…˜์ด ์ „๋‹ฌ๋˜๋ฉด ๊ธฐ์กด ์ƒํƒœ์˜ value์— payload ๊ฐ’์„ ๋”ํ•œ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ฐธ๊ณ ) ์œ„ ์ฝ”๋“œ์—์„œ state = { value: 0 }์€ reducer์˜ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ๋ฆฌ๋•์Šค๋Š” ์ฒ˜์Œ ์‹คํ–‰๋  ๋•Œ reducer๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์„ค์ •ํ•œ๋‹ค.


- Store

Store๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „์ฒด ์ƒํƒœ(state)๋ฅผ ์ €์žฅํ•˜๋Š” ๊ฐ์ฒด์ด๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ํ•˜๋‚˜์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋Š” ํ•˜๋‚˜์˜ store๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ ์ด store๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ณต์œ ๋œ๋‹ค.

๋ฆฌ๋•์Šค์—์„œ๋Š” createStore() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ store๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์—ฌ๋Ÿฌ ๋ฆฌ๋“€์„œ๋ฅผ ํ•˜๋‚˜๋กœ ํ•ฉ์น˜๊ธฐ ์œ„ํ•ด combineReducers()๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

import { createStore, combineReducers } from "redux";
import counterReducer from './counterReducer';

const rootReducer = combineReducers({
counter: counterReducer
});

const store = createStore(rootReducer);

export default store;

์œ„ ์ฝ”๋“œ์—์„œ๋Š” counterReducer๋ฅผ counter ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” reducer๋กœ ๋“ฑ๋กํ•˜์˜€๋‹ค. store๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „์ฒด ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋ฉฐ ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

๋ฆฌ์•กํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ Redux store๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Provider๋กœ App ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ์„œ store๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค.

import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import store from "./store";
import Counter from "./Counter";

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
<Provider store={store}>
<Counter />
</Provider>
);

Provider๋Š” ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ ์ „์ฒด์— Redux store๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ๋ฆฌ๋•์Šค ์ƒํƒœ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.


3. Redux Toolkit

Redux Toolkit์€ ๋ฆฌ๋•์Šค๋ฅผ ๋” ์‰ฝ๊ณ  ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ฆฌ๋•์Šค ๊ณต์‹ ํŒ€์—์„œ ์ œ๊ณตํ•˜๋Š” ์ƒํƒœ ๊ด€๋ฆฌ ๋„๊ตฌ ๋ชจ์Œ์ด๋‹ค. ๊ธฐ์กด ๋ฆฌ๋•์Šค๋Š” store ์„ค์ •, reducer ์ž‘์„ฑ, action ์ƒ์„ฑ ๊ณผ์ •์—์„œ ๋ฐ˜๋ณต์ ์ธ ์ฝ”๋“œ๊ฐ€ ๋งŽ์ด ๋ฐœ์ƒํ•˜๋Š” ๋‹จ์ ์ด ์žˆ์—ˆ์ง€๋งŒ Redux Toolkit์€ ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์—ฌ ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚จ๋‹ค.


Redux Toolkit์˜ ์ฃผ์š” ๋ณ€๊ฒฝ์ 

1. Store ์„ค์ • ๊ฐ„์†Œํ™”
๊ธฐ์กด ๋ฆฌ๋•์Šค์—์„œ๋Š” createStore()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ store๋ฅผ ์ƒ์„ฑํ–ˆ์ง€๋งŒ Redux Toolkit์—์„œ๋Š” configureStore()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ store๋ฅผ ์„ค์ •ํ•œ๋‹ค.

configureStore()๋Š” Redux DevTools๋ฅผ ์ž๋™์œผ๋กœ ์ง€์›ํ•˜๋ฉฐ middleware๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํฌํ•จ๋˜์–ด ์žˆ์–ด ๋ณ„๋„์˜ ์„ค์ •์ด ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค.

import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counterSlice";

const store = configureStore({
reducer: {
counter: counterReducer
}
});

export default store;


2. Reducer ์ž‘์„ฑ ๋ฐฉ์‹ ๊ฐœ์„ 
๊ธฐ์กด ๋ฆฌ๋•์Šค์—์„œ๋Š” switch๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ reducer๋ฅผ ์ž‘์„ฑํ–ˆ์ง€๋งŒ Redux Toolkit์—์„œ๋Š” createSlice()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ reducer์™€ action์„ ๋™์‹œ์— ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋˜ํ•œ ๋‚ด๋ถ€์ ์œผ๋กœ Immer ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒํƒœ๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋„ ๋ถˆ๋ณ€์„ฑ์ด ์ž๋™์œผ๋กœ ์œ ์ง€๋œ๋‹ค.

import { createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
increment: (state, action) => {
state.value += action.payload;
}
}
});

export const { increment } = counterSlice.actions;
export default counterSlice.reducer;


3. Action ์ƒ์„ฑ ์ž๋™ํ™”
Redux Toolkit์€ reducer๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ action creator๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ action ๊ฐ์ฒด๋ฅผ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์ค„์–ด๋“ค์–ด ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•ด์ง„๋‹ค.

import { increment } from "./counterSlice";
import { useDispatch } from "react-redux";

const dispatch = useDispatch();

dispatch(increment(1));
์ด์ „ ๊ธ€
๐Ÿ“ ์›น์‚ฌ์ดํŠธ ์„ฑ๋Šฅ ์ธก์ • ๋„๊ตฌ Lighthouse
๋‹ค์Œ ๊ธ€
๐Ÿ“„ Pageable๋กœ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•˜๊ธฐ (+Spring Data JPA)
์žฅ์‹์šฉ ๋กœ๊ณ