학습 목표
1. Redux 에서 reducer 에 값을 넘기는 방식인 payload 를 이해할 수 있습니다.
2. Ducks 패턴에 대해 이해할 수 있습니다.
Payload 란?
새로 만들 기능
이전 챕터에서 +1 또는 -1 기능을 만들었습니다. 근데 만약 1로 정해진 기능을 만드는 것이 아니라 증가시킬 숫자를 카운터 프로그램을 사용하는 사용자가 직접 정할 수 있게 기능을 만들어보려고 합니다.
다시 말해, 5를 더하고 싶으면 어떤 input 에 5를 입력해서 버튼을 누르면 5가 더해지고, 10을 더하고 싶으면 10을 입력하고 버튼을 눌렀을 때, 10이 더해지는 프로그램입니다.
Payload 란?
이전까지 만든 기능은 리듀서에게 "더 해" 라고만 말을 했습니다. 1이라는 것은 임의적으로 정해서 리듀서에게 액션 객체를 보냈죠.
하지만, 이제 "N을 더해" 라고 N을 같이 리듀서에서 보내야 합니다. 지금가지는 ~을 이라는 목적어가 없었다면, 그 목적어가 생긴 것이고, 목적어도 액션객체에 담아 같이 보내줘야 할 것입니다. 이렇게 액션객체에 같이 담아 보내주는 것을 payload 라고 합니다. 만약 10을 더해 라는 것을 리듀서에게 보내고 싶으면 액션객체에 payload 를 같이 담아주는 것입니다.
코드로 보면 아래와 같습니다. 이제 +1 이 아닌 +n 이므로 type 의 value 로 아래처럼 변경이 되어야 문맥이 맞습니다.
// payload가 추가된 액션객체
{type: "ADD_NUMBER", payload: 10} // type뿐만 아니라 payload라는 key와 value를 같이 담는다.
이렇게 State 를 변경하는 데 있어 만약 리듀서에게 어떤 값을 같이 보내줘야 한다면, payload 를 액션객체에 같이 담아 보냅니다.
🤔 꼭 payload 라는 이름을 통해서 보내야 할까요?
리덕스는 굉장히 유연한 라이브러리이기 때문에 많은 것들이 "표준화" 되어 있지 않습니다. 때문에 자신만의 방식으로 프로그래밍할 수 있는 유연성을 제공하죠.
리덕스 공식 문서를 확인해보면, 액션은 객체이며 해당 액션이 어떤 기능을 수행해야 하는지 명시하는 type 이라는 프로퍼티를 반드시 가져야 한다고 나와 있습니다. 그래서 지금까지는 아래와 같이 액션 객체에 type 프로퍼티를 추가해 어떤 기능을 수행해야 할지 명시해줬습니다.
{type: "ADD_NUMBER"}
하지만 그 외에 데이터들을 어떤 프로퍼티에 값을 넣어줘야 하는지는 개발자 마음이라는거죠.
{type: "ADD_NUMBER", num: 10} // ??
{type: "ADD_NUMBER", number: 10} // ??
{type: "ADD_NUMBER", data: 10} // ??
{type: "ADD_NUMBER", myNumber: 10} // ??
{type: "ADD_NUMBER", myNum: 10} // ??
{type: "ADD_NUMBER", payload: 10}
위에 작성한 코드는 전부 유효한 코드입니다. 그렇지만, 데이터를 payload 프로퍼티에 담아주는 이유는 커뮤니티에서 이렇게 하면, 제일 좋더라 하는 "커뮤니티 best practice" 로 공유되면서 많은 개발자가 데이터는 payload 라는 프로퍼티에 담아주고 있습니다. 이 얘기는 커뮤니티 컨벤션이기 때문에 개발하는 과정에서 얼마든 개발자 취향과 상황에 따라 변경할 수 있음을 알아두어야 합니다.
payload 를 이용해서 기능 구현하기
기능구현 작업 순서
기능 구현은 아래 순서대로 작업할 것입니다.
- 사용자가 입력한 값을 받을 input 구현하기
- Action Creator 작성하기
- 리듀서 작성하기
- 구현된 기능 테스트하기
1. 사용자가 입력한 값을 받을 input 구현하기
사용자가 더하고자 하는 값을 입력할 수 있는 input 을 구현해봅시다. 기존에 App.js 에 작성했던 코드를 모두 지우고 다시 처음부터 구현해보겠습니다.
src/App.js
// src/App.js
import React from "react";
const App = () => {
return (
<div>
<input type="number" />
<button>더하기</button>
<button>빼기</button>
</div>
);
};
export default App;
대략적인 html 을 마크업 해줍니다. input 과 button 2개를 작성합니다.
그리고 input 의 값을 state 로 관리하기 위해 훅을 사용해 useState 를 사용하고, 이벤트핸들러(onChangeHandler)를 작성해 input 과 연결해줍니다.
src/App.js
// src/App.js
import React from "react";
import { useState } from "react";
const App = () => {
const [number, setNumber] = useState(0);
const onChangeHandler = (event) => {
const { value } = event.target;
// event.target.value는 문자열 입니다.
// 이것을 숫자형으로 형변환해주기 위해서 +를 붙여 주었습니다.
setNumber(+value);
};
// 콘솔로 onChangeHandler가 잘 연결되었는지 확인해봅니다.
// input에 값을 넣을 때마다 콘솔에 그 값이 찍히면 연결 성공!
console.log(number);
return (
<div>
<input type="number" onChange={onChangeHandler} />
<button>더하기</button>
<button>빼기</button>
</div>
);
};
export default App;
이번 예시코드에서는 input 의 onChange 핸들러를 input 안에서 인라인으로 작성하지 않고, 위쪽에서 함수를 별도로 분리해 작성하고, 그것을 넣어주는 방식으로 작성해보았습니다.
2. counter.js 모듈 작성: Action Creator
counter.js 로 이동해서 모듈을 작성하겠습니다. 기존에 작성되어 있던 코드를 모두 지우고, 새로 작성해보겠습니다.
먼저 작성해야할 것을 리스트업 해봅니다. 총 5개를 작성해야 합니다. 모듈을 만들 때, 빈 페이지에 만들어야 할 것을 리스트업 하고 작성하는 것이 구성요가 많아 빼먹을 일이 없습니다.
src/redux/modules/counter.js
// src/redux/modules/counter.js
// Action Value
// Action Creator
// Initial State
// Reducer
// export default reducer
Action value 와 Action Creator 를 아래와 같이 작성합니다.
지금까지 작성한 Action Creator 와 조금 차이가 있습니다. payload 가 필요한 Action Creator 에서는 함수를 선언할 때 매개변수 자리에 payload 를 넣어줘야 합니다. Action Creator 를 사용하는 컴포넌트에서 리듀서로 보내고자 하는 payload 를 인자로 넣어줘야 하기 때문입니다.
인자로 payload 를 넣어줌으로써 Action Creator 가 액션객체를 생성할 때 payload 를 같이 담아 생성하는 원리이기 때문입니다.
src/redux/modules/counter.js
// src/redux/modules/counter.js
// Action Value
const ADD_NUMBER = "ADD_NUMBER";
// Action Creator
export const addNumber = (payload) => {
return {
type: ADD_NUMBER,
payload: payload,
};
};
// Initial State
// Reducer
// export default reducer
추가적으로 ES6 에서는 객체의 key 와 value 가 같으면, 아래와 같이 줄여서 작성할 수 있습니다. 앞으로는 이런 축약형태로 코드를 작성하겠습니다.
export const addNumber = (payload) => {
return {
type: ADD_NUMBER,
payload,
};
};
3. counter.js 모듈 작성 : Initial State, Reducer, 내보내기(export default)
Action Creator 를 작성했으니, 이제 리듀서 로직도 작성해보겠습니다. 우선 Initial State 와 리듀서의 기본 형태를 만들어줍니다. 그리고 파일의 마지막 부분에 export default 를 통해서 생성한 리듀서를 내보낼 겁니다.
src/redux/modules/counter.js
// src/redux/modules/counter.js
// .. 중략
// Initial State
const initialState = {
number: 0,
};
// Reducer 기본형태
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
// export default reducer
export default counter;
그리고 이어서 ADD_NUMBER 의 로직을 아래와 같이 구현합니다.
// 리듀서
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;
}
};
사용자가 컴포넌트에서 Action Creator 로 payload 를 담아 보내는 것은 액션 객체에 담겨지고, 그렇게 담겨진 것은 리듀서에서 action.payload 에서 꺼내 사용할 수 있습니다. 그래서 꺼내온 payload 를 기존의 값에 더해줌으로써 기능을 구현하는 겁니다.
4. 구현된 기능 테스트하기
UI 및 counter 모듈을 모두 구현했습니다. 이제 만든 기능이 잘 작동하는지 한 번 테스트 해보겠습니다.
우선, 첫째로 App.js 에서 useSelector 를 이용해서 Store 의 값을 조회하고, 조회한 state 를 렌더링하는 기능을 추가하겠습니다.
src/App.js
// src/App.js
import React from "react";
import { useState } from "react";
import { useSelector } from "react-redux";
const App = () => {
const [number, setNumber] = useState(0);
// 1. 아래 코드 추가 👇
const globalNumber = useSelector((state) => state.counter.number);
const onChangeHandler = (event) => {
const { value } = event.target;
setNumber(+value);
};
return (
<div>
{/* 2. 아래 코드 추가 */}
<div>{globalNumber}</div>
<input type="number" onChange={onChangeHandler} />
<button>더하기</button>
<button>빼기</button>
</div>
);
};
export default App;
두 번째로, Action Creator 를 import 하고, payload 를 담아 dispatch 해보겠습니다. 아래와 같이 코드를 추가합니다. 1번부터 5번까지 순서대로 흐름을 따라가며 작성합니다.
src/App.js
import React from "react";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
// 4. Action Creator를 import 합니다.
import { addNumber } from "./redux/modules/counter";
const App = () => {
// 1. dispatch를 사용하기 위해 선언해줍니다.
const dispatch = useDispatch();
const [number, setNumber] = useState(0);
const globalNumber = useSelector((state) => state.counter.number);
const onChangeHandler = (event) => {
const { value } = event.target;
setNumber(+value);
};
// 2. 더하기 버튼을 눌렀을 때 실행할 이벤트핸들러를 만들어줍니다.
const onClickAddNumberHandler = () => {
// 5. Action creator를 dispatch 해주고, 그때 Action creator의 인자에 number를 넣어줍니다.
dispatch(addNumber(number));
};
return (
<div>
<div>{globalNumber}</div>
<input type="number" onChange={onChangeHandler} />
{/* 3. 더하기 버튼 이벤트핸들러를 연결해줍니다. */}
<button onClick={onClickAddNumberHandler}>더하기</button>
<button>빼기</button>
</div>
);
};
export default App;
모두 작성했다면, 5를 input 에 입력하고, 더하기를 누르면 현재 숫자에서 5를 더하고, 3을 입력하고 더하기를 누르면 현재 숫자에서 3을 더하게 됩니다. 이상 payload 를 활용한 Action Creator 실습이었습니다.
Ducks 패턴
Ducks 패턴이란?
리덕스를 사용하기 위해서는 결국 리덕스의 구성요소를 모두 만들어야만 사용이 가능합니다. 근데 만약 리덕스 모듈을 개발하는 개발자마다 구성요소들을 제 각각 구현하면 어떨까요? 그리고 본인이 그런 개발자와 협업을 해야 하는 상황에 놓였을 때 수많은 파일 중에 본인이 필요로 하는 구성요소를 찾는 것이 쉬울까요?
그래서 'Erick Rasmussn' 이라는 개발자가 이것을 패턴화해 작성하는 것을 제안했는데, 그것이 바로 Ducks 패턴입니다. Ducks 라는 이름은 리덕스의 '덕스' 라고 합니다.
https://github.com/erikras/ducks-modular-redux
GitHub - erikras/ducks-modular-redux: A proposal for bundling reducers, action types and actions when using Redux
A proposal for bundling reducers, action types and actions when using Redux - GitHub - erikras/ducks-modular-redux: A proposal for bundling reducers, action types and actions when using Redux
github.com
Ducks 패턴으로 작성하기
사실 이미 Ducks 패턴으로 코드를 작성하고 있었습니다.
Erick Rasmussen 이 제안한 Duck 패턴은 아래의 내용을 지켜 모듈을 작성하는 것입니다.
- Reducer 함수를 export default 합니다.
- Action Creator 함수들을 export 합니다.
- Action type 은 app/reducer/ACTION_TYPE 형태로 작성합니다.
(외부 라이브러리로서 사용될 경우 또는 외부 라이브러리가 필요로 할 경우에는 UPPER_SNAKE_CASE 로만 작성해도 괜찮습니다.
그래서 모듈 파일 1개에 Action Type, Action Creator, Reducer 가 모두 존재하는 작성방식입니다.
정리해서
- 리듀서로 보내는 액션객체에 어떤 정보를 같이 담아보내고자 한다면, payload 를 이용합니다.
- payload 는 Action Creator 를 생성할 때 매개변수에 자리에서 받을 준비를 하고, 반환하는 액션객체에 payload 라는 key 와 받은 매개변수를 value 로 하여 구현합니다.
- 리듀서에서 payload 를 사용하고자 할 때는 action.payload 로 사용할 수 있습니다.
- ES6 에서 객체를 생성할 때 key 와 value 가 같으면 축약해서 작성할 수 있습니다.
- Ducks 패턴은 Erick Rasmussen 이 제안했고, 현재 리덕스 모듈 작성방법의 정석으로 여겨지고 있습니다.
'항해 16기 > Today I Learned' 카테고리의 다른 글
[항해 23일차] TIL_React 숙련주차 : Redux - Payload 및 Ducks 패턴 (2) | 2023.09.05 |
---|---|
[항해 22일차] TIL_React 숙련주차 : Redux-Refactoring(action creators, action values) (0) | 2023.09.04 |
[항해 20일차] TIL_React 숙련주차 : 카운터 프로그램 만들기2 (0) | 2023.08.30 |
[항해 19일차] TIL_React 숙련주차 : Redux 설정 + 카운터 프로그램 만들기 (0) | 2023.08.30 |
[항해 18일차] TIL_React 숙련주차 : Redux 란? (0) | 2023.08.30 |