Redux๋ ์๋ฐ์คํฌ๋ฆฝํธ ์ ํ๋ฆฌ์ผ์ด์
์ ์ํ๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํ ์คํ ์์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค. ์ฃผ๋ก ๋ฆฌ์กํธ์ ํจ๊ป ์ฌ์ฉ๋์ง๋ง ๋ฆฌ์กํธ์ ์์กดํ์ง ์๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ํ๋ ์์ํฌ๋ ๋ฐ๋๋ผ ์๋ฐ์คํฌ๋ฆฝํธ์์๋ ์ฌ์ฉํ ์ ์๋ค.
๋ฆฌ์กํธ ์ ํ๋ฆฌ์ผ์ด์
์ ๊ท๋ชจ๊ฐ ์ปค์ง์๋ก ์ปดํฌ๋ํธ ๊ฐ ๋ฐ์ดํฐ ์ ๋ฌ์ด ๋ณต์กํด์ง ์ ์์ผ๋ฉฐ ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ์์ ์ปดํฌ๋ํธ๋ก ์ฌ๋ฌ ๋จ๊ณ์ ๊ฑธ์ณ props๋ฅผ ์ ๋ฌํด์ผ ํ๋ ํ์์ Props Drilling์ด๋ผ๊ณ ํ๋ค.
Redux๋ ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ ์ญ ์ํ๋ฅผ ํ๋์ ์ ์ฅ์์์ ๊ด๋ฆฌํ๋๋ก ์ค๊ณ๋์๋ค.
[Redux์ 3๋ ์์น]
Action์ ์ํ ๋ณํ๊ฐ ํ์ํ๋ค๋ ์ฌ์ค์ ์ค๋ช
ํ๋ ๊ฐ์ฒด์ด๋ค. ๋ฆฌ๋์ค์์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๊ธฐ ์ํด์๋ ๋ฐ๋์ Action ๊ฐ์ฒด๋ฅผ ์ ๋ฌํด์ผ ํ๋ฉฐ Action์ ํญ์ type์ด๋ผ๋ ํ์ ์์ฑ์ ๊ฐ์ง๋ค. ๋ํ ํ์์ ๋ฐ๋ผ payload์ ๊ฐ์ ์ถ๊ฐ ๋ฐ์ดํฐ๋ฅผ ํฌํจํ ์ ์๋ค.
const incrementAction = {
type: 'INCREMENT',
payload: 1
};
์ด Action ๊ฐ์ฒด๋ 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๋ ํ์ฌ ์ํ(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๋ ์ ํ๋ฆฌ์ผ์ด์
์ ์ ์ฒด ์ํ(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๋ฅผ ์ ๋ฌํ์ฌ ํ์ ์ปดํฌ๋ํธ๋ค์ด ๋ฆฌ๋์ค ์ํ์ ์ ๊ทผํ ์ ์๋๋ก ํ๋ค.
Redux Toolkit์ ๋ฆฌ๋์ค๋ฅผ ๋ ์ฝ๊ณ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋๋ก ๋ฆฌ๋์ค ๊ณต์ ํ์์ ์ ๊ณตํ๋ ์ํ ๊ด๋ฆฌ ๋๊ตฌ ๋ชจ์์ด๋ค. ๊ธฐ์กด ๋ฆฌ๋์ค๋ store ์ค์ , reducer ์์ฑ, action ์์ฑ ๊ณผ์ ์์ ๋ฐ๋ณต์ ์ธ ์ฝ๋๊ฐ ๋ง์ด ๋ฐ์ํ๋ ๋จ์ ์ด ์์์ง๋ง 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));
