드디어 길고 길었던 음식 주문 앱 실습이 끝나고 리덕스로 들어왔다.
그래도 음식 주문 앱에서 머리 뽀개가며 컨텍스트 api와 useReducer를 배운 덕에
리덕스 공부를 하기가 훨씬 수월하게 느껴져서 보람차다!!
리덕스 무엇일까?
리덕스는 크로스 컴포넌트 앱 또는 앱 와이드 상태를 위한 상태 관리 라이브러리다.
즉, 단일 컴포넌트에서만 사용하는 상태가 아니라 여러 컴포넌트나 앱 전체에서 쓰이는 상태라면 리덕스에서 관리하는 것이 편할 수 있다.
리덕스는 FLUX 패턴을 모델로 한다.
FLUX 패턴은 단방향 방식으로 View에서 Action을 호출하면 Dispatcher를 통해 Store에 데이터가 보관되고 다시 뷰로 전달되는 흐름을 따른다.
아래 그림은 props drilling 방식의 상태 관리와 FLUX 패턴에 따른 상태 관리 방식의 차이점을 보여준다.
(개인적으로 아래 그림을 보면 엄청난 쾌감이 느껴져서 계속 쳐다보며 멍 때리게 된다...)
리덕스로 상태 변경하기
그러면 이제 본격적으로 리덕스로 상태 변경을 해보자.
리덕스 첫 실습은 카운터 애플리케이션으로 하는 게 국룰인 듯하다.
이 카운터는 숫자를 5씩 늘리고 줄이는 Increment, Decrement 기능,
카운터 숫자를 표시했다 감췄다 하는 Toggle 기능이 있다.
먼저 리덕스의 핵심! 상태를 저장하는 스토어 공간을 만들어 보자.
import { createStore } from "redux";
const initialState = { counter: 0, showCounter: true };
const counterReducer = (state = initialState, action) => {
if (action.type === "INCREMENT") {
return {
counter: state.counter + action.amount,
showCounter: state.showCounter,
};
}
if (action.type === "DECREMENT") {
return {
counter: state.counter - action.amount,
showCounter: state.showCounter,
};
}
if (action.type === "TOGGLE") {
return {
counter: state.counter,
showCounter: !state.showCounter,
};
}
return state;
};
const store = createStore(counterReducer);
export default store;
FLUX 패턴에서 보았듯, 리덕스의 핵심 개념은 view-action-reducer-store-view로 흐르는 흐름이다.
view는 사용자가 보는 부분이므로 우리가 리덕스로 구현할 것은 action, reducer, store 이 세가지다.
먼저 상태의 저장공간인 store는 createStore를 통해 만들어 준다.
이 메서드는 현재 deprecated 상태이긴 하지만 계속 사용할 수는 있다.
createStore를 대체하는 리덕스 툴킷의 configureStore는 리덕스 툴킷을 다룰 때 다뤄 보겠다.
createStore는 전달인자로 reducer 함수를 받는다.
reducer 함수에는 두 매개변수 state, action이 있다.
state은 현재 상태의 스냅샷이고, action은 사용자에 의해 촉발된 행동이다.
이렇게 현재 상태와 액션을 받아 액션의 타입에 따라서 상태를 업데이트하는 방식을 설정해 주면 된다.
여기서는 액션의 타입이 'INCREMENT'일 경우 현재 상태의 카운터에 액션의 페이로드를 받아서 더해주고,
액션의 타입이 'DECREMENT'일 경우 현재 상태의 카운터에 액션의 페이로드를 빼 주는 조작을 했다.
액션의 타입이 'TOGGLE'이면 'showcounter'를 토글한 상태를 리턴해 준다.
이때 주의할 점은 절대로 현재 상태 그 자체를 변경하면 안 된다는 것이다.
반드시 새 객체를 선언해서 리턴을 해줘야 불변성을 지키면서 상태를 업데이트할 수 있다.
그러면 카운터 부분으로 가보자.
import classes from "./Counter.module.css";
import { useSelector, useDispatch } from "react-redux";
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector((state) => state.counter);
const incrementHandler = () => {
dispatch({ type: "INCREMENT", amount: 5 });
};
const decrementHandler = () => {
dispatch({ type: "DECREMENT", amount: 5 });
};
const counterIsShown = useSelector((state) => state.showCounter);
const toggleCounterHandler = () => {
dispatch({ type: "TOGGLE" });
};
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
{counterIsShown && <div className={classes.value}>{counter}</div>}
<div>
<button onClick={incrementHandler}>Increment by 5</button>
<button onClick={decrementHandler}>Decrement by 5</button>
</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
export default Counter;
여기서는 액션을 촉발해 주는 dispatch 함수가 핵심이다.
버튼을 클릭하면 각 타입에 따라 dispatch 함수에 액션을 담아 보낼 수 있도록 설정한다.
페이로드가 있다면 액션 객체에 함께 담아서 보내면 된다.
처음에도 말했듯 컨텍스트 api와 useReducer 훅을 배워둬서 기본적인 애플리케이션에서 리덕스를 사용하는 방법은 어렵지 않았다. 다음 포스팅에서는 리덕스를 더욱 편리하게 사용할 수 있게 만들어주는 리덕스 툴킷에 대해서도 알아보자!
'React' 카테고리의 다른 글
[React] 리액트 성능 최적화: React.Memo, useCallback, useMemo (0) | 2023.04.18 |
---|---|
[Redux] 리덕스 툴킷 사용하기 (2) | 2023.04.14 |
[React] useRef (0) | 2023.04.12 |
[React] useReducer (2) | 2023.04.07 |
[React] Context API (0) | 2023.04.06 |