1. Redux 란?
Redux는 JavaScript 애플리케이션의 상태 관리를 위한 예측 가능한 상태 컨테이너 라이브러리입니다. 특히 React와 함께 사용되어 React 애플리케이션에서 상태를 효과적으로 관리하고 데이터 흐름을 예측 가능하게 만듭니다.
Redux가 필요한 이유
(1) useState의 불편함
어떤 컴포넌트에서 생성한 state를 다른 컴포넌트로 보고자 할 때 Props로 State를 공유하는 방법에는 불편한 점이 있습니다.
- 컴포넌트에서 컴포넌트로 State를 보내기위해서는 반드시 부-모 관계가 되어야 한다.
- 조부모 컴포넌트에서 손자 컴포넌트로 값을 보내고자 할때도 반드시 부모 컴포넌트를 거쳐야만 한다. 즉, 정작 부모컴포넌트에서는 그 값이 필요가 없어도 단순히 손자 컴포넌트에게 전달하기 위해 불필요하게 거쳐야만 하는 것을 의미한다. (조부모 → 부모 → 손자)
- 자식 컴포넌트에서 부모 컴포넌트로 값을 보낼 수 없다.
- 리덕스를 사용하면State를 공유하고자 할때 부-모 관계가 아니여도 되고, 중간에 의미없이 컴포넌트를 거치지 않아도 됩니다. 그리고 자식 컴포넌트에서 만든 State를 부모 컴포넌트에서도 사용할 수 있게 됩니다.
(2) Global state와 Local state
Local state (지역상태) 란?
컴포넌트에서 useState를 이용해서 생성한 state 입니다. 좁은 범위 안에서 생성된 State 라고 생각하시면 됩니다.
Global state (전역상태)란?
Global state는 컴포넌트에서 생성되지 않습니다. 중앙화 된 특별한 곳에서 State들이 생성됩니다. 좀 더 쉽게 얘기해서 “중앙 state 관리소” 라고 생각하면 됩니다.
중앙 State관리소에서 State를 생성하고, 만약 어떤 컴포넌트에서 State가 필요하다면 컴포넌트가 어디에 위치하고 있든 상관없이 State를 불러와서 사용 할 수 있게 됩니다. 이렇게 특정 컴포넌트에 종속되어 있는 것이 아니라 “중앙 state 관리소”에서 생성된 State를 Global state라고 합니다. 그리고 이러한 값들을 관리하는 것을 전역 상태 관리 라고 합니다.
2. Redux란 무엇인가?
(1) Redux 정의
우리가 위에서 말한 “중앙 state 관리소”를 사용할 수 있게 도와주는 패키지(라이브러리) 입니다.
프론트엔드 개발자들은 “리덕스”를 전역 상태관리 라이브러리 라고 많이 표현합니다. 전역 상태, 즉 Global State를 의미하고 그것을 관리하게 도와주는 라이브러리 (패키지) 이기 때문입니다.
2. Redux 설정
(1) Redux 설치
리액트에서 리덕스를 사용하기 위해서는 2개의 패키지를 설치해야 합니다. vscode 터미널에서 아래 명령어를 입력해서 2개의 패키지를 설치하세요. 참고로 react-redux 라는 패키지는 리덕스를 리액트에서 사용할 수 있도록 서로 연결시켜주는 패키지 입니다.
yarn add redux react-redux
아래와 같은 의미
yarn add redux
yarn add react-redux
(2) 폴더 구조 생성하기
- src 폴더 안에 redux 폴더를 생성
- redux 폴더 안에 config, modules 폴더를 생성
- config 폴더 안에 configStore.js파일을 생성합니다.
각각의 폴더와 파일은 역할이 있습니다.
- redux : 리덕스와 관련된 코드를 모두 모아 놓을 폴더 입니다.
- config : 리덕스 설정과 관련된 파일들을 놓을 폴더 입니다.
- configStore : “중앙 state 관리소" 인 Store를 만드는 설정 코드들이 있는 파일 입니다.
- modules : 우리가 만들 State들의 그룹이라고 생각하면 됩니다. 예를 들어 투두리스트를 만든다고 한다면, 투두리스트에 필요한 state들이 모두 모여있을 todos.js를 생성하게 되텐데요, 이 todos.js 파일이 곧 하나의 모듈이 됩니다.
(2) src/configStore.js
- src/configStore.js 에 아래 코드를 입력하세요.
import { createStore } from "redux";
import { combineReducers } from "redux";
/*
1. createStore()
리덕스의 가장 핵심이 되는 스토어를 만드는 메소드(함수) 입니다.
리덕스는 단일 스토어로 모든 상태 트리를 관리한다고 설명해 드렸죠?
리덕스를 사용할 시 creatorStore를 호출할 일은 한 번밖에 없을 거예요.
*/
/*
2. combineReducers()
리덕스는 action —> dispatch —> reducer 순으로 동작한다고 말씀드렸죠?
이때 애플리케이션이 복잡해지게 되면 reducer 부분을 여러 개로 나눠야 하는 경우가 발생합니다.
combineReducers은 여러 개의 독립적인 reducer의 반환 값을 하나의 상태 객체로 만들어줍니다.
*/
const rootReducer = combineReducers({});
const store = createStore(rootReducer);
export default store;
(3) index.js
- 디렉토리의 가장 최상단에 있는 index.js에 아래 내용을 입력하세요.
// 원래부터 있던 코드
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
// 우리가 추가할 코드
import store from "./redux/config/configStore";
import { Provider } from "react-redux";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
//App을 Provider로 감싸주고, configStore에서 export default 한 store를 넣어줍니다.
<Provider store={store}>
<App />
</Provider>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
3. 모듈 만들기
modules 폴더에 counter.js 파일을 생성한다.
// src/modules/counter.js
// 초기 상태값
const initialState = {
number: 0,
};
// 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
// 모듈파일에서는 리듀서를 export default 한다.
export default counter;
4. 모듈의 구성요소 살펴보기
(1) initialState === 초기 상태값
// 초기 상태값
const initialState = {
number: 0,
};
- 이것은 initialState 입니다. 단어 그대로 초기 상태값 입니다. 즉, 어떤 State의 초기값을 정해주는 것 입니다.
const [number, setNumber] = useState(0) // < 여기
- 위 코드에서만든 State의 초기값은 { } (객체) 이고, 그 안에 number 라는 변수에 초기값 0을 할당해준 것입니다. 초기값은 꼭 객체가 아니어도 됩니다. 배열이 되어도 되고, 그냥 원시데이터가 돼도 됩니다. 그리고 객체에도 여러개의 변수를 넣어줄 수 있습니다.
// 초기값이 0
const initialState = 0;
// 초기값이 0이 있는 배열
const initialState = [0];
// 초기값이 number = 0, name = '석구'인 객체
const initialState = {
number: 0,
name: '석구'
};
(2) Reducer === 변화를 일으키는 함수
// 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
아래 코드 처럼 number값을 변경할 수 있었습니다.
// 예시 코드
const onClickHandler = () => {
setNumber(number + 1); // setState를 이용해서 state 변경
}
리덕스에서는 리듀서가 이 역할을 합니다.
우리가 “리듀서야 number에 +1를 해줘" 라고 명령하면, 리듀서는 number에 +1을 더해줍니다.
그래서 변화를 일으키는 함수라고 표현한 것 입니다.
// src/redux/modules/counter.js
// counter 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
export default counter; // 여기
그리고 리듀서의 인자에 보면 (state = intialState, action) 이라고 되어 있습니다.
우리는 리듀서 인자 첫번째 자리에서는 state를, 두번째 자리에서는 action 이라는 것을 꺼내서 사용할 수 있습니다.
(3) 카운터 모듈을 스토어에 연결하기
지금까지 모듈파일에서 초기 상태값과 리듀서를 작성했습니다. 이제 만든 모듈을 스토어에 연결 시켜야 합니다. 아직까진 모듈과 스토어가 각각 따로 분리되어 있는 상태이기 때문에 우리가 만든 State를 스토어에서 꺼낼 수 없습니다.
- configStore.js로 이동해서 아래 코드를 추가해주세요.
// src/redux/modules/config/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. 스토어와 모듈 연결 확인하기
(1) useSelector = 스토어 조회
우리가 생성한 모듈을 스토어에 잘 연결했는지 확인하는 방법은 컴포넌트에서 스토어를 직접 조회하면 됩니다. 컴포넌트에서 리덕스 스토어를 조회하고자 할때는 useSelector라는 ‘react-redux’의 훅을 사용해야 합니다.
- useSelector의 사용법은 아래와 같습니다.
// 1. store에서 꺼낸 값을 할당 할 변수를 선언합니다.
const number =
// 2. useSelector()를 변수에 할당해줍니다.
const number = useSelector()
// 3. useSelector의 인자에 화살표 함수를 넣어줍니다.
const number = useSelector( ()=>{} )
// 4. 화살표 함수의 인자에서 값을 꺼내 return 합니다.
// 우리가 useSelector를 처음 사용해보는 것이니, state가 어떤 것인지 콘솔로 확인해볼까요?
const number = useSelector((state) => {
console.log(state)
return state
});
- App.js 컴포넌트로 이동해서 기존에 있던 코드를 모두 지우고, 아래 코드를 입력해주세요.
// src/App.js
import React from "react";
import { useSelector } from "react-redux"; // import 해주세요.
const App = () => {
const counterStore = useSelector((state) => state); // 추가해주세요.
console.log(counterStore); // 스토어를 조회해볼까요?
return <div></div>;
}
export default App;
브라우저를 켜고, 콘솔을 보면 아래 이미지처럼 객체가 보이고, 그 안에 counter 라는 값이 있는 것을 볼 수 있습니다. 우리가 만든 counter 라는 모듈의 state가 보이는 것을 알 수 있습니다. 이렇게 화살표 함수에서 꺼낸 state라는 인자는 현재 프로젝트에 존재하는 모든 리덕스 모듈의 state 인 것 입니다.
이제 어떤 컴포넌트에서도 접근 할 수 있는 스토어를 가지게 되었습니다. 만약 컴포넌트에서 number라는 값을 사용하고자 한다면 아래 코드처럼 꺼내서 사용하면 됩니다.
const number = useSelector(state => state.counter.number); // 0
3. Refactoring(action creators, action values)
1. Action Creator
(1) Action Creator 란?
- 만약에 우리가 액션객체의 value를 변경할 일이 생긴다면 어떨까요? PLUS_ONE , MINUS_ONE 이라는 value 대신 이 액션객체가 counter 모듈안에 있다는 것을 강조하기 위해서 counter/PLUS_ONE, counter/MINUS_ONE 이라는 value로 바꾸길 각각 바꾸길 원한다면 아래 코드에서 4군데를 변경해줘야 할 것 입니다. 근데 또 만약에 그게 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,
};
// action.type이 MINUS_ONE 일 때 새로운 state 반환
case "MINUS_ONE": // counter/MINUS_ONE로 변경
return {
number: state.number - 1,
};
default:
return state;
}
};
// 모듈파일에서는 리듀서를 export default 한다.
export default counter;
(2) Action Creator 만들기
- 그래서 앞으로는 위 코드처럼 직접 하드코딩을 하는 것이 아니라, 액션객체를 한 곳에서 관리할 수 있도록 “함수"와 액션 value를 상수로 만들어보겠습니다.
- 만약 PLUS_ONE 이라는 액션 객체를 만드는 함수를 만든다면 아래와 같이 만들 수 있습니다. 그리고 우리는 이것을 Action Creator라고 부릅니다. 해석 그대로 액션을 만드는 생성자 인 것이죠.
// src/redux/modules/counter.js
const PLUS_ONE = "PLUS_ONE"; // value는 상수로 생성
// 액션객체를 반환하는 함수 생성
// export 가 붙는 이유는 plusOne()는 밖으로 나가서 사용될 예정이기 때문입니다.
export const plusOne = () => {
return {
type: PLUS_ONE, // type에는 위에서 만든 상수로 사용 (vscode에서 자동완성 지원)
};
};
- 이렇게 액션의 value는 상수로 따로 만들어주고, 그리고 그것을 이용해서 액션객체를 반환하는 함수를 작성합니다. 그리고 이것을 실제로 리듀서와 컴포넌트에서는 아래와 같이 작성합니다.
- 리듀서 전체 코드를 볼까요?
- 모듈에 initialState와 리듀서밖에 없었지만 액션의 value와 Action Creator가 추가되었습니다.
// src/modules/counter.js
// 추가된 코드 👇 - 액션 value를 상수들로 만들어 줍니다. 보통 이렇게 한곳에 모여있습니다.
const PLUS_ONE = "PLUS_ONE";
const MINUS_ONE = "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: // case에서도 문자열이 아닌, 위에서 선언한 상수를 넣어줍니다.
return {
number: state.number + 1,
};
case MINUS_ONE: // case에서도 문자열이 아닌, 위에서 선언한 상수를 넣어줍니다.
return {
number: state.number - 1,
};
default:
return state;
}
};
export default counter;
(3) Action Creator 사용하기
- 우리가 생성한 Action Creator를 컴포넌트에서 어떻게 사용하는지 알아보겠습니다.
- 사용하는 방법은 아래순서로 진행합니다.
- export된 Action Creator import 하기
- dispatch()에 있던 액션객체를 지우고, Action creator 넣기
- 하나씩 코드로 보겠습니다. 우선 우리가 사용할 Action creator를 import 해야 합니다. 즉 Action creator는 태생적으로 counter.js 밖에서 사용될 함수들이었습니다. 그래서 생성할때부터 앞에 export 를 붙여준 것이었죠.
- 그리고 dispatch()안에 있던 액션객체를 import 한 Action Creator들로 변경해줍니다. 이렇게 하면 수정이 모두 끝났습니다.
// src/App.js
import React from "react";
import { useDispatch, useSelector } from "react-redux";
// 사용할 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(plusOne()); // 액션객체를 Action creator로 변경합니다.
}}
>
+ 1
</button>
{/* 빼기 버튼 추가 */}
<button
onClick={() => {
dispatch(minusOne()); // 액션객체를 Action creator로 변경합니다.
}}
>
- 1
</button>
</div>
);
};
export default App;
2. 왜 Action creator를 사용해야 하나?
(1) 휴먼에러 (오타) 방지
- 액션객체의 type value를 상수로 만들어놓았기 때문에, 개발툴에서 자동완성등의 보조 기능을 지원받을 수 있습니다. 그래서 의도치 않은 휴먼에러(오타)를 없앨 수 있어요.
(2) 유지보수의 효율성 증가
- 우리가 만든 Action Creator가 만약 100군데에서 쓰이고 있는 상태에서 혹여나 그것을 바꾸어야 하는 상황이 오더라도 단 한번의 수정으로 100군데에 모든 수정사항을 반영할 수 있습니다.
(3) 코드 가독성
- 모듈 파일에서 Action Creator가 일목요연하게 정리가 되어있으면, 내가 아닌 다른 개발자가 보았을 때 해당 모듈이 가지고 있는 모든 Action들을 한눈에 알 수 있게 됩니다. 즉 그 자체가 Action 들의 리스트업을 해주는 역할을 갖게 되는 것 입니다.
정리
- 액션객체를 만드는 함수를 Action Creator(액션 크리에이터)라고 한다.
- Action Creator는 모듈 파일안에서 생성된다.
- 액션객체의 type value로 상수로 생성해서 관리한다.
- Action Creator를 사용하면, 여러가지 문제점을 해소할 수 있다.
4. Payload 및 Ducks 패턴
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"}
- 하지만 그 외에 데이터들을 어떤 프로퍼티에 값으로 넣어줘야 하는지는 개발자 마음이라는거죠.
- 공식문서
💡 A plain object describing the change that makes sense for your application. ... Actions must have a type field that indicates the type of action being performed. Types can be defined as constants and imported from another module. It's better to use strings for type than Symbols because strings are serializable. ❗️ Other than type, the structure of an action object is really up to you. If you're interested, check out Flux Standard Action for recommendations on how actions could be constructed.
{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라는 프로퍼티에 담아주고 있습니다. 이 얘기를 드리는 이유는 커뮤니티 컨벤션이기 때문에 개발하는 과정에서 얼마든 개발자 취향과 상황에 따라 변경할 수 있음을 알려드리고 싶었습니다.
2. payload를 이용해서 기능 구현하기
(1) payload를 이용해서 기능 구현 작업 순서
- 기능 구현은 아래 순서대로 작업할 것 입니다.
- 사용자가 입력한 값을 받을 input 구현하기
- Action Creator 작성하기
- 리듀서 작성하기
- 구현된 기능 테스트 하기
(2) 사용자가 입력한 값을 받을 input 구현하기
- 대략적인 html을 마크업 해줍니다. input과 button 2개를 아래와 같이 작성합니다.
// src/App.js
import React from "react";
const App = () => {
return (
<div>
<input type="number" />
<button>더하기</button>
<button>빼기</button>
</div>
);
};
export default App;
- 그리고 input의 값을 state로 관리하기 위해 훅을 사용하여 state 사용하고, 이벤트핸들러 (onChangeHandler)를 작성하여 input과 연결해줍니다.
// 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;
(3) counter.js 모듈 작성: Action Creator
- Action value와 Action Creator를 아래와 같이 작성합니다.
- 지금까지 작성한 Action Creator와 조금 차이가 있습니다. payload가 필요한 Action Creator에서는 함수를 선언할 때 매개변수 자리에 paylaod를 넣어줘야 합니다. 왜냐하면 Action Creator를 사용하는 컴포넌트에서 리듀서로 보내고자 하는 payload를 인자로 넣어줘야 하기 때문입니다.
- 인자로 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,
};
};
// Initial State
// Reducer
// export default reducer
- ✅ 추가적으로 ES6에서는 객체의 key와 value가 같으면 아래와 같이 줄여서 작성할 수 있습니다. 앞으로는 이런 축약형태로 코드를 작성하겠습니다.
export const addNumber = (payload) => {
return {
type: ADD_NUMBER,
payload,
};
};
(4) counter.js 모듈 작성: Initial State, Reducer, 내보내기(export default)
- Action Creator를 작성했으니, 이제 리듀서 로직도 작성해보겠습니다. 우선 Initial State와 리듀서의 기본 형태를 만들어줍니다. 그리고 파일의 마지막 부분에 export default 를 통해서 생성한 리듀서를 내보내줄게요.
// 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;
}
};
(5) 구현된 기능 테스트 하기
- UI 및 counter 모듈을 모두 구현해보았습니다. 이제 우리가 만든 기능이 잘 작동하는지 한번 테스트 해볼게요.
- 우선, 첫째로 App.js에서 useSelector를 이용해서 Store의 값을 조회하고 그것을 화면상에 렌더링하는 기능을 추가하겠습니다.
// 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번까지 순서대로 흐름을 따라가며 작성해보세요.
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 기능이였습니다.
- 다음으로는 Ducks 패턴에 대해 살펴보겠습니다.
3. Ducks 패턴
(1) Ducks 패턴이란?
- 리덕스를 사용하기 위해서는 결국 우리가 리덕스의 구성요소를 모두 만들어야만 사용이 가능합니다. 근데 만약 리덕스 모듈을 개발하는 개발자마다 구성요소들을 제 각각 구현하면 어떨까요? 그리고 내가 그 개발자와 협업을 해야 하는 상황에 놓였을 때 수많은 파일 중에 내가 필요로 하는 구성요소를 찾는것이 쉬울까요?
- 그래서 Erik Rasmussn 라는 개발자가 이것을 패턴화하여 작성하는 것을 제안했는데, 그것이 바로 Ducks패턴입니다. Ducks 라는 이름은 리덕스의 덕스 라고 하네요. 심볼은 오리 입니다. 🐤
(2) Duck 패턴으로 작성하기
- Erik Rasmussen 이 제안한 Ducks 패턴은 아래의 내용을 지켜 모듈을 작성하는 것 입니다.
- 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 패턴은 Erik Rasmussen 이 제안했고, 현재 리덕스 모듈 작성방법의 정석으로 여겨지고 있다.
현업에서도 비중을 많이 차지하는 상태 관리 라이브러리인 만큼 React를 배우는 사람들이라면 꼭 알아두어야 겠죠!?
이상으로 Redux 정리 마치겠습니다. 추후에는 Redux toolkit을 이용해 Redux의 단점을 줄이는 방법을 소개해 드리겠습니다!
'React' 카테고리의 다른 글
React - Redux Toolkit(feat. Redux Devtools) 사용하기 (1) | 2023.11.27 |
---|---|
React - React Router Dom 기능 및 동적 라우팅 적용 방법 (1) | 2023.11.27 |
React - React Hooks 의 종류와 사용 방법 (2) | 2023.11.27 |
React - Styled Components(feat. GlobalStyles) 이용해보기 (0) | 2023.11.24 |
React - 개발환경 세팅 (0) | 2023.11.23 |