액션 객체의 type 값을 변경해야 하는 상황 가정함.
PLUS_ONE , MINUS_ONE 대신 counter 모듈안에 있다는 것을 강조하기 위해 counter/PLUS_ONE, counter/MINUS_ONE 형태로 변경하고 싶을 수 있음.
문제는 수정 포인트가 많아진다는 점임. 아래 코드 기준으로 최소 4군데 수정이 필요함.
만약 프로젝트 규모가 커서 100군데라면 유지보수 난이도가 급상승함.
// src/App.js
import React from "react";
import { useDispatch, useSelector } from "react-redux";
const App = () => {
const dispatch = useDispatch();
const number = useSelector((state) => state.counter.number);
return (
<div>
{number}
<button
onClick={() => {
dispatch({ type: "PLUS_ONE" }); // ✅️ counter/PLUS_ONE로 변경
}}
>
+ 1
</button>
<button
onClick={() => {
dispatch({ type: "MINUS_ONE" }); // ✅️ counter/MINUS_ONE로 변경
}}
>
- 1
</button>
</div>
);
};
export default App;
// src/modules/counter.js
// 초기 상태값
const initialState = {
number: 0,
};
// 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
case "PLUS_ONE": // ✅️ counter/PLUS_ONE로 변경
return {
number: state.number + 1,
};
case "MINUS_ONE": // ✅️ counter/MINUS_ONE로 변경
return {
number: state.number - 1,
};
default:
return state;
}
};
export default counter;
위 코드처럼 직접 하드코딩을 하는 것이 아니라, 액션을 만들어주는 함수(액션객체를 반환하는 함수)를 사용함.
이 함수를 Action Creator라고 부름. 말 그대로 액션 생성 함수임.
또한 type 값은 문자열이 아닌 상수로 관리함.
// src/modules/counter.js
// ✅️ 추가 코드 - 상수로 생성.
const PLUS_ONE = "PLUS_ONE";
const MINUS_ONE = "MINUS_ONE";
// ✅️ 추가 코드 - Action Creator 생성
// export 가 붙는 이유는 plusOne()는 밖으로 나가서 사용될 예정이기 때문.
export const plusOne = () => {
return {
type: PLUS_ONE, // type에는 위에서 만든 상수로 사용
};
};
export const minusOne = () => {
return {
type: MINUS_ONE,
};
};
// 초기 상태값
const initialState = {
number: 0,
};
// 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
case PLUS_ONE: // ✅️ case에 문자열이 아닌, 위에서 선언한 상수를 넣어줌.
return {
number: state.number + 1,
};
case MINUS_ONE: // ✅️ case에 문자열이 아닌, 위에서 선언한 상수를 넣어줌.
return {
number: state.number - 1,
};
default:
return state;
}
};
export default counter;
생성한 Action Creator를 컴포넌트에서 사용하는 법
// src/App.js
import React from "react";
import { useDispatch, useSelector } from "react-redux";
// 1. export된 Action Creator import 하기
import { minusOne, plusOne } from "./redux/modules/counter";
const App = () => {
const dispatch = useDispatch();
const number = useSelector((state) => state.counter.number);
return (
<div>
{number}
<button
onClick={() => {
// dispatch({ type: "PLUS_ONE" });
dispatch(plusOne()); // 2. 액션객체를 Action creator로 변경
}}
>
+ 1
</button>
{/* 빼기 버튼 추가 */}
<button
onClick={() => {
// dispatch({ type: "MINUS_ONE" });
dispatch(minusOne()); // 2. 액션객체를 Action creator로 변경
}}
>
- 1
</button>
</div>
);
};
export default App;
Q. dispatch()안에는 반드시 객체만 들어가야 한다고 알고 있는데, 어떻게 함수가 들어감?
A. {type: “PLUS_ONE”} === plusOne() 는 같은 값임.
함수 실행 결과는 객체임. 함수 자체를 넘기는 것이 아님. 함수 실행 결과를 넘기는 것임.
자동완성 지원으로 오타 가능성 감소.
수정 포인트 단일화. 100군데 사용 중이어도 함수 하나만 수정하면 됨.
모듈이 가진 액션 목록 한눈에 파악 가능.
즉, 액션들을 리스트업 해주는 역할을 하므로 협업 시 이해도 상승.
기존 액션은 의미만 전달함. “더해라” 수준의 명령임.
이제는 값도 함께 전달해야 함.(예: “n을 더해라”)
이때 사용하는 값이 payload.
// payload가 추가된 액션객체
{type: "ADD_NUMBER", payload: 10}
Q. 꼭 payload라는 이름을 사용해야함?
A. 유연하게 사용가능함. type만 필수. 그러나 payload 사용이 관례임. 가독성과 협업 측면에서 유리함.
// 이렇게 사용 가능하지만
{type: "ADD_NUMBER", number: 10}
{type: "ADD_NUMBER", data: 10}
{type: "ADD_NUMBER", myNumber: 10}
// 이렇게 사용을 추천
{type: "ADD_NUMBER", payload: 10}
기능 구현 작업 순서
// src/App.js
import React from "react";
import { useState } from "react";
const App = () => {
// 1. input의 값을 state로 관리하기 위해 훅을 사용하여 state 사용.
const [number, setNumber] = useState(0);
const onChangeHandler = (event) => {
const { value } = event.target;
// event.target.value는 문자열.
// 이것을 숫자형으로 형변환해주기 위해서 +를 붙여 줌.
setNumber(+value);
};
// 3. 콘솔로 onChangeHandler가 잘 연결되었는지 확인.
// input에 값을 넣을 때마다 콘솔에 그 값이 찍히면 연결 성공.
console.log(number);
return (
<div>
/* 2. 이벤트핸들러 (onChangeHandler)를 작성하여 input과 연결 */
<input type="number" onChange={onChangeHandler} />
<button>더하기</button>
<button>빼기</button>
</div>
);
};
export default App;
payload가 필요한 Action Creator는 함수를 선언할 때 매개변수 자리에 paylaod를 넣어줌.
인자로 payload를 넣어줌으로써 Action Creator가 액션객체를 생성할때 payload를 같이 담아 생성하는 원리.
// src/redux/modules/counter.js
// Action Value
const ADD_NUMBER = "ADD_NUMBER";
// Action Creator
export const addNumber = (payload) => {
return {
type: ADD_NUMBER,
payload: payload,
// ES6에서는 key와 value가 같으면 이렇게 축약가능
// payload,
};
};
// src/redux/modules/counter.js
// ... Action Value, Action Creator 생략
// Initial State
const initialState = {
number: 0,
};
// Reducer
const counter = (state = initialState, action) => {
switch (action.type) {
case ADD_NUMBER:
return {
// state.number(기존의 nubmer)에 action.paylaod(유저가 더하길 원하는 값)을 더함.
number: state.number + action.payload,
};
default:
return state;
}
};
// export default reducer
export default counter;
// src/App.js
import React from "react";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
// 5. Action Creator를 import.
import { addNumber } from "./redux/modules/counter";
const App = () => {
// 2. dispatch를 사용하기 위해 선언.
const dispatch = useDispatch();
const [number, setNumber] = useState(0);
// 1. Store의 값을 조회하고 그것을 화면상에 렌더링
const globalNumber = useSelector((state) => state.counter.number);
const onChangeHandler = (event) => {
const { value } = event.target;
setNumber(+value);
};
// 3. 더하기 버튼을 눌렀을 때 실행할 이벤트핸들러를 생성.
const onClickAddNumberHandler = () => {
// 6. Action creator를 dispatch 해주고,
// 그때 Action creator의 인자에 number를 넣음.
dispatch(addNumber(number));
};
return (
<div>
<div>{globalNumber}</div>
<input type="number" onChange={onChangeHandler} />
{/* 4. 더하기 이벤트핸들러를 연결. */}
<button onClick={onClickAddNumberHandler}>더하기</button>
<button>빼기</button>
</div>
);
};
export default App;
input에 숫자 입력 → 버튼 클릭 → 입력값만큼 증가 확인
Redux 구조 정리 방법론 중 하나.
Action Type, Action Creator, Reducer 이 세 가지를 한 파일에 작성함.
개발자마다 구조가 다르면
협업 난이도 상승함, 구성요소 찾기 어려움, 유지보수 비용 증가함.
이를 해결하기 위해 Erik Rasmussen이 제안함.
🔗 Erik Rasmussn의 Ducks 제안
https://github.com/erikras/ducks-modular-redux
export default 한다.export 한다.app/reducer/ACTION_TYPE 형태로 작성한다.결과적으로 Redux 관련 코드(Action Type, Action Creator, Reducer)가 한 파일에 존재하는 작성방식. 모듈 단위 관리가 가능함.