리덕스는 리액트에서 가장 많이 사용되는 상태 관리 라이브러리중 하나이다. 리덕스를 사용하면 컴포넌트의 상태 업데이트 관련 로직을 다른 파일로 분리시켜서 효율적으로 관리할 수 있다. 최근 Redux Toolkit이 등장하면서 더욱 더 이 효율성은 빛을 보이고 있다.
이전에 useReducer 를 썼을 때와 비슷한 구조를 가지고 있다. +1 버튼을 누르면 수가 증가하고, -1 버튼을 누르면 수가 감소하는 카운터를 구현한다고 생각해보자.
Action Type 정의
어떤 action 이 있으면 좋을지 생각해서 정의 해놓는다.
export const INC_COUNT = "INC_COUNT";
export const DEC_COUNT = "DEC_COUNT";
변수 형태로 미리 만들어놓는 이유
Action 생성 함수
원칙적으로 항상 액션 객체를 리턴해야 한다!
{ type: INC_COUNT, payload: ~~ }
와 같은 객체를 redux 에서는 action
객체라고 말한다.
이러한 action
객체를 reducer
로 보내는 행위를 dispatch
라고 표현한다.
dispatch 에 action 객체를 직접 명시해서 전달하기도 하지만, dispatch 에 action 객체를 Return 하는 함수를 전달해서 사용하는 경우도 있다.
이렇게 action 객체를 return 하는 함수를 action 생성 함수라고 표현한다.
export function incCount(diff) {
return {
type: INC_COUNT,
payload: {diff},
}
}
export function decCount(diff) {
return {
type: DEC_COUNT,
payload: {diff},
}
}
ex) 만약 버튼을 눌렀을 때 수가 2씩 증가하도록 하는 action 객체를 생성해서 reducer 로 보내고싶다면? → dispatch(incCount(2))
라고 실행한다.
state 는 불변성을 유지해야 한다. 동일한 파라미터가 들어왔다면 동일한 결과를 출력해야 한다. (date 등을 사용할 경우 해당 부분은 액션 생성 시 처리하고, 리듀서에서 그러한 랜덤 로직을 사용해서는 안됨)
function counter(state = initialState, action) {
switch (action.type) {
case INC_COUNT:
return state + 1
case DEC_COUNT:
return state - 1;
default:
return state;
}
}
store
란 앞서 만든 리듀서와 상태를 담아놓은 것이다. 이 store
를 Provider
라는 것을 통해서 컴포넌트에 공유함으로써, 어떤 컴포넌트에서든 리듀서와 상태를 가져와서 사용할 수 있도록 코드를 작성한다.
import { legacy_createStore as createStore } from "redux";
import counter from "./reducers/counter";
const store = createStore(counter);
export default store;
useSelector 는 redux 로 관리 중인 상태를 가져오기 위한 Hook이다.
state 안에 number 라는 데이터가 있다면, 아래처럼 가져올 수 있다. dispatch 를 통해서 state 가 변하게 되면, 자동으로 useSelector 를 통해서 받아온 state 도 변하게 된다.
import { useSelector } from 'react-redux'
const { number } = useSelector(state => state)
useDispatch 는 dispatch 함수를 실행할 수 있도록 해주는 Hook 이다. 만들어진 액션 객체를 Reducer 로 전달하는 과정을 수행해주는 Hook 이라고 생각하면 된다.
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
// 사용 예시
dispatch(incCount(1))
import { legacy_createStore as createStore } from "redux";
import counter from "./reducers/counter";
const store = createStore(counter);
export default store;
legacy_createStore
가 아닌 그냥 createStore
의 경우 deprecate 되었다고 한다. redux 개발자들이 redux toolkit 사용을 권장하기 위해서 해당 함수를 deprecate 시켰으나, 사용 빈도를 보면 createStore
를 쓰는 경우도 아직 많은 것으로 보인다.
기존패턴은 이렇게 폴더를 나누어서 코드를 작성하는 방법이다.
하지만 ducks 패턴은 하나의 파일에다가 모든 코드를 작성하는 것을 말한다. 코드 작성이 조금 더 용이해지고, 폴더 구조 상의 가독성이 더 좋아진다는 장점이 있다.
[규칙]
default export
해야 한다.export
해야 한다.// 액션 타입
const INC_COUNT = "counter/INC_COUNT";
const DEC_COUNT = "counter/DEC_COUNt";
// 액션 생성 함수
export function incCount(diff) {
return {
type: INC_COUNT,
payload: {diff},
}
}
export function decCount(diff) {
return {
type: DEC_COUNT,
payload: {diff},
}
}
// 초기값
const initialState = {number: 0};
// 리듀서 선언
export default function counter(state = initialState, action) {
switch (action.type) {
case INC_COUNT:
return {
...state,
number: state.number + action.payload.diff
}
case DEC_COUNT:
return {
...state,
number: state.number - action.payload.diff
};
default:
return state;
}
}