1) Redux가 필요한 이유
useState를 통해 컴포넌트에서 생성한 state를 다른 컴포넌트로 보내고자 할 때 Props를 통해서 부모 컴포넌트에서 자식 컴포넌트로 그 값을 보내준다. 하지만 props로 state를 공유하는 방법에는 불편함 점 존재한다.
➡️ 컴포넌트에서 컴포넌트로 state를 보내기 위해서는 반드시 부-모 관계가 되어야 한다.
➡️ 조부모 컴포넌트에서 손자 컴포넌트로 값을 보내고자 할 때에도 반드시 부모 컴포넌트를 거쳐야만 한다. (정작 부모 컴포넌트에서는 그 값이 필요가 없어도 단순히 손자 컴포넌트에게 전달하기 위해 불필요하게 거쳐야만 하는 것이다.)
➡️ 자식 컴포넌트에서 부모 컴포넌트로 값을 보낼 수 없다.
💡 리덕스를 사용하면 state를 공유하고자 할 때 부-모 관계가 아니여도 되고, 중간에 의미 없이 컴포넌트를 거치지 않아도 된다. 또한 자식 컴포넌트에서 만든 state를 부모 컴포넌트에서도 사용할 수 있다!
2) Global state와 Local state
useState로 생성한 state는 Local State이고, Redux에서 생성한 State는 Global State이다.
3) Redux란?
✅ 리덕스는 전역 상태 관리 라이브러리이다.
✅ 리덕스는 중앙 state 관리소를 가지고 있으며, 모든 state는 이곳에서 생성된다.
4) 폴더구조
redux : 리덕스 관련 코드를 모두 몰아넣음
config : 리덕스 설정 관련 파일 전부
configStore : 중앙 state 관리소와 관련된 설정 코드
modules : 중앙 state에서 관리하는 state의 그룹
4-1) 중앙 데이터 관리소(store)를 설정
configStore.js
import { createStore } from "redux";
import { combineReducers } from "redux";
const rootReducer = combineReducers({});
const store = createStore(rootReducer);
4-2) store를 애플리케이션에 주입
configStore.js
export default store;
index.js
import { Provider } from "react-redux";
import store from "./redux/config/configStore";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<App />
</Provider>
);
📌 export default는 import 할 때 {} 필요 없음!
5-1) Redux로 Counter 만들기
./modules/counter.js
const initialState = {
number: 0,
};
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
export default counter;
configStore.js
import { createStore } from "redux";
import { combineReducers } from "redux";
import counter from "../modules/counter";
const rootReducer = combineReducers({
counter: counter,
});
const store = createStore(rootReducer);
export default store;
5-2) state에서 store에 있는 counter에 접근하기 (useSelector)
App.jsx
import { useSelector } from "react-redux";
import "./App.css";
function App() {
const data = useSelector((state) => {
return state.counter;
});
console.log("data", data);
return <div>Redux!</div>;
}
export default App;
5-3) store에 접근해서 counter의 값을 변경하기 (useDispatch)
App.jsx
iimport { useDispatch, useSelector } from "react-redux";
import "./App.css";
function App() {
const counter = useSelector((state) => {
return state.counter;
});
// dispatch 가져오기
const dispatch = useDispatch();
return (
<>
<div>현재 카운트 : {counter.number}</div>
<button
onClick={() => {
// +1을 해주는 로직을 써준다.
dispatch({
type: "PLUS_ONE",
});
}}
>
+
</button>
<button
onClick={() => {
// -1을 해주는 로직을 써준다.
dispatch({
type: "MINUS_ONE",
});
}}
>
-
</button>
</>
);
}
export default App;
counter.js
const initialState = {
number: 0,
};
const counter = (state = initialState, action) => {
switch (action.type) {
case "PLUS_ONE":
return {
number: state.number + 1,
};
case "MINUS_ONE":
return {
number: state.number - 1,
};
default:
return state;
}
};
export default counter;
6-1) Counter 리팩토링 (Action Value)
counter.js
// action value
export const PLUS_ONE = "counter/PLUS_ONE";
export const MINUS_ONE = "counter/MINUS_ONE";
const initialState = {
number: 0,
};
const counter = (state = initialState, action) => {
switch (action.type) {
case PLUS_ONE:
return {
number: state.number + 1,
};
case MINUS_ONE:
return {
number: state.number - 1,
};
default:
return state;
}
};
export default counter;
App.jsx
import "./App.css";
import { useDispatch, useSelector } from "react-redux";
import { PLUS_ONE, MINUS_ONE } from "./redux/modules/counter";
function App() {
const counter = useSelector((state) => {
return state.counter;
});
// dispatch 가져오기
const dispatch = useDispatch();
return (
<>
<div>현재 카운트 : {counter.number}</div>
<button
onClick={() => {
// +1을 해주는 로직을 써준다.
dispatch({
type: PLUS_ONE,
});
}}
>
+
</button>
<button
onClick={() => {
// -1을 해주는 로직을 써준다.
dispatch({
type: MINUS_ONE,
});
}}
>
-
</button>
</>
);
}
export default App;
6-2) Counter 리팩토링 (Action Creator)
counter.js
// action value
export const PLUS_ONE = "counter/PLUS_ONE";
export const MINUS_ONE = "counter/MINUS_ONE";
// action creator
export const plusOne = () => {
return {
type: PLUS_ONE,
};
};
export const minusOne = () => {
return {
type: MINUS_ONE,
};
};
const initialState = {
number: 0,
};
const counter = (state = initialState, action) => {
switch (action.type) {
case PLUS_ONE:
return {
number: state.number + 1,
};
case MINUS_ONE:
return {
number: state.number - 1,
};
default:
return state;
}
};
export default counter;
App.jsx
import "./App.css";
import { useDispatch, useSelector } from "react-redux";
import { plusOne, minusOne } from "./redux/modules/counter";
function App() {
const counter = useSelector((state) => {
return state.counter;
});
// dispatch 가져오기
const dispatch = useDispatch();
return (
<>
<div>현재 카운트 : {counter.number}</div>
<button
onClick={() => {
// +1을 해주는 로직을 써준다.
dispatch(plusOne());
}}
>
+
</button>
<button
onClick={() => {
// -1을 해주는 로직을 써준다.
dispatch(minusOne());
}}
>
-
</button>
</>
);
}
export default App;
7) payload (전달되는 실체)
🤔 예를 들어 payload가 3이라는 것은 '3만큼을 플러스, 마이너스해라' 라는 뜻
Counter.js
// action value
const PLUS_N = "counter/PLUS_N";
const MINUS_N = "counter/MINUS_N";
// action creator
export const plusN = (payload) => {
return {
type: PLUS_N,
payload: payload,
};
};
export const minusN = (payload) => {
return {
type: MINUS_N,
payload: payload,
};
};
const initialState = {
number: 0,
};
const counter = (state = initialState, action) => {
switch (action.type) {
case PLUS_N:
return {
number: state.number + action.payload,
};
case MINUS_N:
return {
number: state.number - action.payload,
};
default:
return state;
}
};
export default counter;
App.js
import "./App.css";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { plusN, minusN } from "./redux/modules/counter";
function App() {
// useState
const [number, setNumber] = useState(0);
const counter = useSelector((state) => {
return state.counter;
});
const dispatch = useDispatch();
return (
<>
<div>현재 카운트 : {counter.number}</div>
<div>
<input
type="number"
value={number}
onChange={(event) => {
const { value } = event.target;
setNumber(+value);
}}
/>
</div>
<button
onClick={() => {
dispatch(plusN(number));
}}
>
+
</button>
<button
onClick={() => {
dispatch(minusN(number));
}}
>
-
</button>
</>
);
}
export default App;
✅ action 객체는 type과 payload가 있다.
✅ action 객체는 payload만큼 type에 맞게 처리한다.
✅ action 객체를 store로 dispatch가 던진다.
8) Ducks 패턴
리덕스를 사용하기 위해서는 우리가 리덕스의 구성요소를 모두 만들어야만 한다. 근데 만약 리덕스 모듈을 개발하는 개발자마다 구성요소들을 다르게 구현한다면?
이를 피해기 위해 만들어진 패턴이 바로 Ducks 패턴
➡️ 모듈 파일 1개에 Action type, Action Creator, Reducer가 모두 존재해야 함