Redux
์ ์ญ ์ํ ๊ด๋ฆฌ๋ฅผ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, flux ๊ท๊ฒฉ์ reducer๊ฐ ๊ฒฐํฉ๋ ํจํด์ด๋ค. Flux๋ณด๋ค ๋์ฑ ๊ฐ๋จํ๋ค๋ ์ ์ด ํน์ง์ด๋ค.
* Flux ํจํด
Facebook์์ ๋ง๋ ์ํคํ ์ฒ ํจํด, ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ดํฐ ํ๋ฆ์ ๋จ๋ฐฉํฅ์ผ๋ก ์ ์งํ๊ธฐ ์ํ ๊ตฌ์กฐ
๋ฐ์ดํฐ ํ๋ฆ : View → Action → Dispatcher → Store → View ์ ๋ฐ์ดํธ
Redux์ ๋ฐ์ดํฐ ํ๋ฆ
๊ตฌ์ฑ์์
- Action : ์ํ๋ฅผ ์ด๋ป๊ฒ ๋ณํ์ํฌ ๊ฒ์ธ์ง์ ๋ํ ๋ด์ฉ์ ๋ด์ ๊ฐ์ฒด
↓ - Dispatch : ์ก์
๊ฐ์ฒด๋ฅผ Reducer๋ก ๋๊ฒจ์ฃผ๋ ์ญํ ์ ํ๋ ํจ์
↓ - Reducer : ๋ฆฌํดํ๋ ๊ฐ์ด ์๋ก์ด ์ํ๊ฐ ๋๋ ํจ์
์ฌ๋ฌ ๊ฐ๋ผ๋ฉด combineReducers ํจ์๋ก ๋ฌถ์ด ํ๋๋ก ๋ง๋ค ์ ์์ - Store : Redux์ ์ ์ญ ์ํ ์ ์ฅ์, createStore ํจ์์ Reducer๋ฅผ ์ ๋ฌํด ์์ฑ
๋ง๋ค์ด์ง ๊ตฌ์ฑ์์๋ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ๋ค.
- App๊ณผ ์ ์ญ ์ํ ์ ์ฅ์ ์ฐ๊ฒฐ (์ฃผ๋ก Main.jsx์์)
<Provider store={store}> - useSelector()๋ก ์ํ ์ ์ฅ์์์ ์ํ ๊บผ๋ด์ ์ฌ์ฉ
- useDispatch()๋ก dispatch ํจ์ ๋ง๋ค์ด์ ์ฌ์ฉ
+ Flux์ Redux์ ์ฐจ์ด
Flux | Redux | |
Dispatcher | ๋ฐ๋์ ํ์ | Reducer๋ก ๋์ฒด |
Store | ์ฌ๋ฌ ๊ฐ ์กด์ฌ ๊ฐ๋ฅ | 1๊ฐ |
State ๋ณ๊ฒฝ | Store ์์์ ์ง์ ๋ณ๊ฒฝ | ๋ถ๋ณ ์ํ, Reducer๊ฐ ์ ์ํ ๋ฐํ |
Redux-Thunk
์ก์ ์ผ๋ก ๊ฐ์ฒด ๋ง๊ณ ๋ ํจ์๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ํด์ฃผ๋ ๋ฏธ๋ค์จ์ด
=> Redux์์ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ํ ์ ์๊ฒ ํด์ค๋ค!
์ฌ์ฉ ๋ฐฉ๋ฒ
์ค์น
npm i redux react-redux
Redux-thunk ์ค์น
npm i redux-thunk
์์ 1 - ๊ธฐ๋ณธ
App.jsx
import { useSelector, useDispatch } from 'react-redux';
import { combineReducers, legacy_createStore } from 'redux';
import './App.css';
// ์ก์
๊ฐ์ฒด ์ ์
const increment = {
type: 'increment',
};
const decrement = {
type: 'decrement',
};
// Reducer ์ ์
// ์ด๊ธฐ๊ฐ, action์ ํ๋ผ๋ฏธํฐ๋ก ์ค์
const counterReducer = (state = 0, action) => {
// action์ type์ ๋ฐ๋ผ ์ํ ๋ณ๊ฒฝ
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
return state;
}
};
// Store ์ ์, ์ ํ๋ฆฌ์ผ์ด์
์ ์ฒด ์ํ ๊ด๋ฆฌ ๊ฐ์ฒด
// legacy_createStore์ ์ค์ต์ฉ์ผ๋ก ์ฌ์ฉ... ์์๋ redux toolkit์ ๊ถ์ฅ
export const store = legacy_createStore(counterReducer);
function App() {
// useSelector๋ก ํ์ฌ ์ํ๋ฅผ ๊บผ๋ด์ด
const counter = useSelector((state) => state);
// useDispatch๋ก ์ก์
์ store๋ก ๋ณด๋
const dispatch = useDispatch();
// ํด๋ฆญ ์ dispatch()๋ฉ์๋๋ก ์ก์
์ ๋ณด๋ด ์ํ ๋ณ๊ฒฝ!
return (
<>
<div>Counter : {counter}</div>
<button onClick={() => dispatch(increment)}>+</button>
<button onClick={() => dispatch(decrement)}>-</button>
</>
);
}
export default App;
Main.jsx
import { createRoot } from 'react-dom/client';
import './index.css';
import App, { store } from './App.jsx';
import { Provider } from 'react-redux';
createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App />
</Provider>,
);
์์ 2 - Reducer๊ฐ ๋ ๊ฐ ์ด์
์ง๊ธ์ reducer๊ฐ ํ๋์ง๋ง ๋ ๊ฐ ์ด์์ผ ๊ฒฝ์ฐ combineReducers์ ์ฌ์ฉํด ๋ค์๊ณผ ๊ฐ์ด ๊ด๋ฆฌํ ์ ์๋ค.
import { useSelector, useDispatch } from 'react-redux';
import { combineReducers, legacy_createStore } from 'redux';
import './App.css';
// ์ก์
๊ฐ์ฒด
const increment1 = { type: 'increment1' };
const decrement1 = { type: 'decrement1' };
const increment2 = { type: 'increment2' };
const decrement2 = { type: 'decrement2' };
// Reducer1
const counterReducer1 = (state = 4, action) => {
switch (action.type) {
case 'increment1':
return state + 1;
case 'decrement1':
return state - 1;
default:
return state;
}
};
// Reducer2
const counterReducer2 = (state = 0, action) => {
switch (action.type) {
case 'increment2':
return state + 2;
case 'decrement2':
return state - 2;
default:
return state;
}
};
// combineReducers()๋ก ์ฌ์ฉํ Reducer๋ค์ ํฉ์นจ
const rootReducer = combineReducers({ counterReducer1, counterReducer2 });
// ์์ฑํ Store์ Reducer๋ค์ ํฉ์น rootReducer๋ฅผ ๋๊ฒจ์ค
export const store = legacy_createStore(rootReducer);
function App() {
// ์ํ ๊ฐ์ ธ์ค๊ธฐ(Reducer๋ณ๋ก ๊ฐ์ ธ์์ผํจ)
const counter1 = useSelector((state) => state.counterReducer1);
const counter2 = useSelector((state) => state.counterReducer2);
// dispatch ํจ์ ๊ฐ์ ธ์ค๊ธฐ
const dispatch = useDispatch();
return (
<>
<div>Counter 1 : {counter1}</div>
<button onClick={() => dispatch(increment1)}>+</button>
<button onClick={() => dispatch(decrement1)}>-</button>
<hr />
<div>Counter 2 : {counter2}</div>
<button onClick={() => dispatch(increment2)}>+</button>
<button onClick={() => dispatch(decrement2)}>-</button>
</>
);
}
export default App;
์์ 3 - Redux-thunk ์ฌ์ฉ
- Thunk๋ฅผ ๋ฏธ๋ค์จ์ด๋ก ์ ์ฉ => applyMiddleware(thunk)๋ฅผ ํตํด store์ ๊ธฐ๋ฅ ์ถ๊ฐ
- dispatch์ ํจ์ ์ ๋ฌ์ด ๊ฐ๋ฅํด์ง
๋ง๋ค์ด์ง ํจ์๋ dispatch์ getState๋ฅผ ์ธ์๋ก ๋ฐ์ ์ ์๋๋ฐ, ์๋ ์์์์ ์์
+ getState๋ ๋ณดํต ์กฐ๊ฑด์ ์ผ๋ก ์ํ๋ฅผ ๋ณด๊ณ ์ก์ ์ ๊ฒฐ์ ํ๊ณ ์ถ์ ๋ (ex. counter๊ฐ 10 ๋ฏธ๋ง์ผ ๋ ์ก์ ์ ์คํํ๊ณ ์ถ์ ๋) ์ฐ์ - ํจ์ ์์์ ๋น๋๊ธฐ ์ฝ๋ ์คํ
- ๋น๋๊ธฐ ์์ ์๋ฃ ํ ์ค์ ์ก์ ๊ฐ์ฒด๋ฅผ dispatch
import { applyMiddleware, combineReducers, legacy_createStore } from 'redux';
import { thunk } from 'redux-thunk';
// ์ค๋ต
// thunk๋ฅผ ๋ฏธ๋ค์จ์ด๋ก ์ ์ฉํ๊ณ , applyMiddleware(thunk)๋ก store์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ค!
export const store = legacy_createStore(rootReducer, applyMiddleware(thunk));
function App() {
const counter1 = useSelector((state) => state.counterReducer1);
const counter2 = useSelector((state) => state.counterReducer2);
// useDispatch๋ก ์ก์
์ ์ก ํจ์ ์์ฑ
const dispatch = useDispatch();
// 2์ด ๋ค ์ฆ๊ฐ์์ผ์ฃผ๋ ํจ์
const imcrement2sec = () => {
return (dispatch) => {
// 2์ด ๋ค increment1์ dispatchํ๋ค
setTimeout(() => {
dispatch(increment1);
}, 2000);
};
};
// thunk ๋๋ถ์ dispatch๊ฐ ํจ์๋ฅผ ์ธ์๋ก ๋ฐ์ ์ ์์!!
return (
<>
<div>Counter 1 : {counter1}</div>
<button onClick={() => dispatch(imcrement2sec())}>+</button>
<button onClick={() => dispatch(decrement1)}>-</button>
</>
);
}
Redux Toolkit
Redux๋ฅผ ๋ ํธํ๊ฒ ์ฐ๊ธฐ ์ํ ๊ณต์ ํดํท์ผ๋ก, ์์๋ Redux ๊ณต์์์๋ ์ด Toolkit์ ์ฐ๊ธฐ๋ฅผ ๊ถ์ฅํ๊ณ ์๋ค.
Redux์์ ์ฌ์ฉ ๋ฐฉ์ ์ฐจ์ด
Redux | Redux Toolkit |
Action | createSlice |
Reducer | |
Store | configureStore |
๊ณตํต์ผ๋ก ์ฌ์ฉ๋๋ ๋ถ๋ถ
- <Provider store={store}></Provider>
- useSelector()
- useDispatch()
createSlice()
์ก์ ํ์ , ์ก์ ์์ฑ์, Reducer๋ฅผ ํ ๋ฒ์ ์์ฑ
createSlice({name:'counter', initalState:0, reducers:{
increment(state, action){state+=1},
decrement(state, action){state-=1}
}})
configureStore()
Reducer๋ฅผ ์ ๋ฌ๋ฐ์ ์ ์ญ ์ํ ์ ์ฅ์๋ฅผ ์์ฑ
const store = configureStore({
reducer:{
counter:counterSlice.reducer,
// ์ฌ๋ฌ๊ฐ์ผ ๊ฒฝ์ฐ
any:anySlice.reducer,
// ... ๋ฑ๋ฑ ์ด๋ ๊ฒ ์์ฑํด์ฃผ๋ฉด ์ฌ๋ฌ ๊ฐ๋ฅผ ์ ๋ฌํด์ค ์ ์์
}
})
์ก์ ์์ฑ์ ์ฌ์ฉ
createSlice์์ ์ ์ํ reducers ์ด๋ฆ์ผ๋ก ์ฌ์ฉ
dispatch(counterSlice.actions.increment());
createAsyncThunk()
Redux toolkit์์ ์ ๊ณตํ๋ Thunk ์์ฑ ํจ์๋ผ ๋ฐ๋ก ์ค์นํ ํ์๊ฐ ์์ผ๋ฉฐ Redux-Thunk์ฒ๋ผ ๋น๋๊ธฐ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํจ
- ๋น๋๊ธฐ ๋ก์ง์ store ์ธ๋ถ๋ก ๋ถ๋ฆฌ
- ์ธ์ : (์ก์ ํ์ ๋ฌธ์์ด, ํ๋ก๋ฏธ์ค๋ฅผ ๋ฐํํ๋ ๋น๋๊ธฐ ํจ์, ์ถ๊ฐ ์ต์ )
์ฌ์ฉ ๋ฐฉ๋ฒ
์ค์น
npm install @reduxjs/toolkit react-redux
์์ 1 - ๊ธฐ๋ณธ
import { useDispatch, useSelector } from 'react-redux';
import './App.css';
import { configureStore, createSlice } from '@reduxjs/toolkit';
// ์ํ + ์ก์
์์ฑ
// createSlice() ์ฌ์ฉ
const counterSlice = createSlice({
name: 'counter', // slice์ ์ด๋ฆ
initialState: 4, // ์ด๊ธฐ ์ํ ๊ฐ
reducers: { // ์ํ ๋ณ๊ฒฝ ํจ์๋ค (reducer + action ์ญํ )
increment(state, action) {
return state + 1;
},
decrement(state, action) {
return state - 1;
},
},
});
// ์คํ ์ด ์์ฑ
// configureStore() ์ฌ์ฉ
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
});
function App() {
// store์์ slice์ ์ด๋ฆ์ธ counter๋ก ์ ๊ทผํด์ ์ํ๋ฅผ ๊ฐ์ ธ์ด
const counter = useSelector((state) => state.counter);
// ์ก์
์ ๋ฐ์์ํค๋ ํจ์
const dispatch = useDispatch();
return (
<>
<div>Counter : {counter}</div>
<button
// counterSlice.actions๋ ์ก์
๊ฐ์ฒด ๋ชจ์
dispatch(counterSlice.actions.increment());
}}>
+
</button>
<button
onClick={() => {
dispatch(counterSlice.actions.decrement());
}}>
-
</button>
</>
);
}
export default App;
์์ 2 - Thunk ์ฌ์ฉํ๊ธฐ
const IncrementThunk2sec = createAsyncThunk(
'counter/slowIncrement', // ์ก์
ํ์
// payloadcreator ํจ์๋ก, ์ฌ๊ธฐ์ ๋น๋๊ธฐ ์์
์ด ์ํ ๊ฐ๋ฅ
// value : thunk๋ฅผ dispatch(IncrementThunk2sec(๊ฐ))์ฒ๋ผ ์ฌ์ฉ ์ ๋๊ธฐ๋ ๊ฐ
(value, { dispatch }) => {
setTimeout(() => {
dispatch(counterSlice.actions.increment());
}, 2000);
},
);
function App() {
const counter = useSelector((state) => state.counter);
const dispatch = useDispatch();
return (
<>
<div>Counter : {counter}</div>
<button
onClick={() => {
dispatch(IncrementThunk2sec());
}}>
+
</button>
</>
);
}
์ด๋ฏธ์ง ์ถ์ฒ
Redux ํจํด : https://qiita.com/dondoko-susumu/items/5a5cb4250b46738c9e48#redux
'react' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
React / React Developer Tools (0) | 2025.04.24 |
---|---|
React / ์ต์ ํ ํจ์ (0) | 2025.04.23 |
React / ์ ์ญ ์ํ ๊ด๋ฆฌ (1) - ๋ด๋ถ (0) | 2025.04.18 |
React / openweatherapi ์ฌ์ฉํ๊ธฐ (0) | 2025.04.17 |
React / Todo-List ๋ง๋ค๊ธฐ (2) (0) | 2025.04.17 |