오늘은 리액트와 리덕스를 함께 사용할 때 권장되는 공식 라이브러리인 react-redux 라이브러리를 알아보고, 이를 사용해 간단한 카운터 예제를 만들도록 하겠다.
Redux 에 대한 내용은 아래 포스팅을 통해 정리해두었다.
🤔react-redux 라이브러리란 무엇인가?
리덕스는 독립적인 라이브러리로 여러 UI 라이브러리 혹은 프레임워크와 함께 사용할 수 있는데, 예시로 Vue, Angular, Ember, React 그리고 바닐라 JS가 있다. 이 UI 프레임워크와 함께 사용할 수 있도록 개별적인 라이브러리를 제공하고 있고, react-redux도 그 중 하나이다. react-redux는 리덕스와 리액트를 함께 사용했을 때 편리할 수 있도록 여러 hooks를 제공한다.
예시로 이런 라이브러리 없이 리덕스를 적용하면, 직접 store를 만들고 그 store에 subscribe로 콜백함수를 넘겨주어야 UI와 store의 연결을 생성할 수 있다. 이런 작업들은 코드가 늘어날수록 반복되고 복잡해지는데, react-redux 에서 제공하는 컴포넌트와 hooks 가 store와 UI가 상호작용 하는 부분의 로직을 상당부분 대신 해주기 때문에 최적화된 코드를 아주 쉽게 작성할 수 있다.
코드 예시
1. Reducer 생성
// countReducer.js
// 초기 상태값 선언
const count = 0;
// 현재 state와 action을 받아와 type별로 분기하는 로직을 작성
// 파일을 분리하였으므로 이 함수는 export 해준다.
export const countReducer = (state = count, action) => {
switch (action.type) {
case 'PLUS':
return state + 1;
case 'MINUS':
return state - 1;
default:
return state;
}
};
2. Store 생성
// index.js
// Provider 컴포넌트와 현재 deprecated 된 createStore API를 import한다.
import { legacy_createStore as createStore } from 'redux';
// 아까 만들어주었던 reducer 함수를 import 한다.
import { countReducer } from './countReducer';
// createStore에 reducer함수를 전달해 store 객체를 생성한다.
const store = createStore(countReducer);
// Provider 컴포넌트로 redux를 적용할 컴포넌트를 감싸고, store을 전달한다.
root.render(
<Provider store={store}>
<Counter />
</Provider>
);
- 현재는 Redux Toolkit 사용이 권장되고 있어서 Vanila redux 는 그다지 많이 사용하지는 않는다고 한다. 다만 돌아가는 원리를 알아야 툴킷도 이해하고 사용할 수 있다.
3. Action Creator 생성
// countAction.js
// 이벤트에 따라 Action 객체를 생성해줄 생성자 함수를 정의한다.
// 파일이 분리되어 있으므로 각각의 함수를 export 한다.
export const countPlus = () => {
return {
type: 'PLUS',
};
};
export const countMinus = () => {
return {
type: 'MINUS',
};
};
4. 이벤트 핸들러, Dispatch 및 State 값 사용
// Counter.js : Counter UI 부분
// action 전달과 state에 접근할 수 있는 hooks를 import 한다.
import { useDispatch, useSelector } from 'react-redux';
// 앞서 생성한 Action Creator 를 import 한다.
import { countPlus, countMinus } from './countAction';
// Counter 컴포넌트
export default function Counter() {
// useSelector와 useDispatch 는 hooks의 규칙에 따라 리액트 컴포넌트 내부에서 호출한다.
// 아래와 같은 문법으로 store에 저장된 state를 불러올 수 있다. 만약 여러개의 reducer가 있다면,
// (state) => state.reducerName 과 같이 접근할 수 있다.
const state = useSelector((state) => state);
// useDispatch 함수를 사용해 dispatch를 실행할 함수를 생성한다.
// useDispatch 가 dispatch 함수의 생성자같은 느낌이라, useDispatch(action) 이렇게 쓰면 X!
const dispatch = useDispatch();
// 이벤트 핸들러를 정의한다. 입력값에 따라 그에 맞는 Action Creator를 실행해 그 값을
// dispatch에 실어 store로 보낸다.
const handleClickButton = (e) => {
const operator = e.target.textContent;
if (operator === '+') dispatch(countPlus());
else if (operator === '-') dispatch(countMinus());
};
return (
<>
<h1>count : {state}</h1>
<button onClick={handleClickButton}>+</button>
<button onClick={handleClickButton}>-</button>
</>
);
}
실수했던 부분
- 위 Counter.js 주석에도 작성했지만, hooks를 사용하는 과정에서 놀랍게도... 컴포넌트 외부에서 작성해놓고 맞왜틀을 시전했다. 리액트 공식문서에도 잘 나와있지만, 오직 리액트 컴포넌트 안에서만 hooks를 사용하도록 되어있다. useSelector와 useDispatch도 리액트 훅의 일종이기 때문에 그 규칙을 따라야 에러가 발생하지 않는다 :)...
- 또 주석에도 작성했지만... useDispatch 함수를 실행해 dispatch 함수를 생성하지 않고, 곧바로 useDispatch(action) 이렇게 사용했다. 찾아보니 useDispatch hook은 dispatch 기능을 하는 함수를 만드는 생성자 같은 함수라서, 함수의 실행 값이 dispatch 함수이기 때문에 위와 같은 문법으로 사용해주어야한다. 이 외에도 함수를 생성하는 hooks 가 여러개 있기 때문에 사용시 주의가 필요할 듯 하다.
- 또 별도로 combineReducers 함수를 사용하지 않은 경우, reducer는 하나이고 reducer를 통해 정의된 state도 하나이기 때문에, useSelector에서 state에 접근하기 위해서 (state) => state.countReducer 이렇게 접근하면 상태값에 제대로 접근할 수 없다. 주의...!
처음 리덕스를 사용하면서 상태관리가 정말 편리해졌다고 느꼈는데, 그 다음으로 리덕스 툴킷을 사용해보니 코드가 이보다 훨씬! 더 적다. 다음은 리덕스 툴킷을 공부해서 같은 내용을 툴킷으로 리팩토링하는 연습을 해보겠다...!
'Stacks > Redux' 카테고리의 다른 글
[Redux] redux-toolkit 폴더 구조 / 비동기처리 - createAsyncThunk 사용법 (0) | 2023.01.17 |
---|---|
[Redux] 상태관리 라이브러리 : Redux 원리 살펴보기 (0) | 2022.09.03 |