지난 포스트에서는 React에서 redux를 다루면서 알아야 하는 기본적인 hook과 메서드들에 대한 학습을 했다.
이번 포스트에서는 redux만으로 관리하기 힘든 커다란 프로젝트에서 사용하기 용이한 redux-toolkit에 대해서 설명해보려고 한다.
npm 설치 (@reduxjs/toolkit)
이전 글에서 설명했지만, npm으로 아래와 같이 입력하여 설치해주면 redux toolkit을 사용할 수 있다.
npm install @reduxjs/toolkit
설치가 끝났다면, 이제 좀 더 개선된 redux-toolkit을 사용할 수 있다.
reducer 대신 slider?
redux에서는 slice라는 개념이 추가되었고 이를 주로 사용한다.
학습에 앞서 먼저 slice에 대한 개념을 알아보자.
Redux에서는 reducer와 별도로 slice를 생성할 수 있다.
slice는 reducer를 생성할 뿐만 아니라 거기다 여러 기능이 확장된 버전이라고 생각하면 된다.
slice를 만들 때 slice를 구분하는 이름과, 초기 state와 reducer 등을 지정할 수 있다. couter관련 reducer들이 모여있기 때문에 이름은 couter로 하였다.
store/index.js (store)
import { configureStore, createSlice } from "@reduxjs/toolkit";
const initialState = { counter: 0, showCounter: true };
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
increase(state, action) {
state.counter = state.counter + action.amount;
},
toggleCounter(state) {
state.showCounter = !state.showCounter;
},
},
});
const store = configureStore({ reducer: counterSlice.reducer });
export default store;
기존에 if문을 사용하던 코드 대신 메서드 명으로 action을 구분할 수 있다.
store 같은 경우는 여전히 configureStore를 사용해주었다.
action 연결하기
이제 createSlice에서 정의한 리듀서 영역의 메서드 이름과, 컴포넌트에서 발송된 action을 매칭 해주어야 한다.
여기서는 만들어낸 slice에 포함된 actions 객체를 이용하면 된다.
만들어진 slice의 actions 객체에는 위에 slice를 만들 때 정의한 메서드들이 전부 들어있다.
export 하기
만들어진 actions들을 파일 맨 밑에서 내보 내보자. store를 내보내듯이 export 해주면 된다.
export const counterActions = counterSlice.actions;
import 해 사용하기
이제 action이 필요한 컴포넌트로 가서 꺼내 주면 된다.
Counter.js
import { useDispatch, useSelector } from "react-redux";
import { counterActions } from "../store";
import classes from "./Counter.module.css";
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector((state) => state.counter);
const show = useSelector((state) => state.showCounter);
const incrementHandler = () => {
dispatch(counterActions.increment());
};
const increaseHandler = () => {
dispatch(counterActions.increase(10));
};
const decrementHandler = () => {
dispatch(counterActions.decrement());
};
const toggleCounterHandler = () => {
dispatch(counterActions.toggleCounter());
};
// ...
여기서 인자로 증가할 값을 reducer로 전달해주어야 하는 increaseHandler를 살펴보자.
const increaseHandler = () => {
dispatch(counterActions.increase(10)); // { type: SOME_UNIQUE_IDENTIFIER, payload: 10}
};
인자로 별도의 설정 없이 그냥 증가할 값인 10을 넣어주었다. 이렇게 입력할 시 toolkit에서 자동으로 type(SOME_UNIQUE_IDENTIFIER)과 필드명(payload)을 지정해준다.
이제 정상적으로 기존과 동일하게 작동하는 것을 확인할 수 있다.
toolkit을 이용하여 코드를 재구조화하여 오타를 줄이고 가독성을 높이며 유지보수를 편하게 변환해보았다.
여러 개의 slice를 관리하기
하나의 slice만 가진 예시가 아닌, 유저 로그인 상태를 저장하는 authSlice를 만들었다고 하자.
slice 추가
store/index.js (store)
// ... 기존 counterSlice 코드 생략
const initialAuthState = { isAuthenticated: false };
const authSlice = createSlice({
name: "authentication",
initialAuthState,
reducers: {
login(state) {
state.isAuthenticated = true;
},
logiut(state) {
state.isAuthenticated = false;
},
},
});
// ...
slice는 여러 개를 생성할 수 있지만, store는 단 하나이다. 하지만 지금의 store는 루트 reducer로 counterReducer 하나만 정의해두었다.
const store = configureStore({ reducer: counterSlice.reducer });
이 코드를
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
auth: authSlice.reducer,
},
});
이렇게 식별자 이름으로 나눠서 변경하면 하나의 store에 여러 slice의 reducer를 함께 정의할 수 있다.
useSelector로 특정 slice 가져오기
slice를 루트로 지정해두지 않고 식별자 이름으로 구분하여 지정하였기 때문에 useSelector로 찾을 때도 루트로 찾는 것이 아닌 식별자 이름을 한 번 거쳐서 찾아야 한다.
Counter.js
const counter = useSelector((state) => state.counter.counter);
const isAuth = useSelector((state) => state.auth.isAuthenticated);
…그리고 store 코드 분리
store 코드 역시나… 앱이 커지면 slice가 계속 추가될 거고, store는 slice들로 길어지게 될 것이다
slice파일들을 아래와 같이 쪼개서 관리하면 훨씬 깔끔할 것이다.
store/slices/counter-slice.js
import { createSlice } from "@reduxjs/toolkit";
const initialCounterState = { counter: 0, showCounter: true };
const counterSlice = createSlice({
name: "counter",
initialState: initialCounterState,
reducers: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
increase(state, action) {
state.counter = state.counter + action.payload;
},
toggleCounter(state) {
state.showCounter = !state.showCounter;
},
},
});
// 액션 생성자
export const counterActions = counterSlice.actions;
export default counterSlice;
store/slices/auth-slice.js
import { createSlice } from "@reduxjs/toolkit";
const initialAuthState = { isAuthenticated: false };
const authSlice = createSlice({
name: "authentication",
initialState: initialAuthState,
reducers: {
login(state) {
state.isAuthenticated = true;
},
logiut(state) {
state.isAuthenticated = false;
},
},
});
// 액션 생성자
export const authActions = authSlice.actions;
export default authSlice;
store/index.js (store)
import { configureStore } from "@reduxjs/toolkit";
import authSlice from "./slices/auth-slice";
import counterSlice from "./slices/counter-slice";
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
auth: authSlice.reducer,
},
});
export default store;
이로서 react context를 대체할 수 있는 상태 관리 라이브러리인 redux를 공부해보았다.
물론 redux뿐만 아니라 여러 가지 상태 관리 라이브러리들이 존재한다.
사실 다음 팀 프로젝트엔 recoil을 사용해보려고 하는데 벌써 두근두근하다😁