간단한 counter app을 구현하면서 redux toolkit을 사용해보자.
기존의 redux와 다른점이 꽤 많으니 머리가 복잡해질 수 있기 때문에 바닥부터 토대를 다져보자.
그냥 React로 버튼을 두 개 만들어 보았다. redux는 전혀 사용된 것이 없다.
// ./App.js
import React from "react";
const App = () => {
return (
<div>
<button>increment</button>
<button>decrement</button>
<button onClick={() => dispatch(incrementByAmount(5))}>
</div>
);
};
export default App;
// ./store/store.js
import { configureStore } from '@reduxjs/toolkit'; // 1.
import counterReducer from './counter'; // 3.
export default configureStore({ // 2.
reducer: {
counter: counterReducer,
},
});
(1). redux toolkit은 configureStore를 통해 store를 생성하게 된다.
(2). configureStore를 통해 reducer들을 모아준다. 마치 combineReducers 처럼 하지만 같지 않다.
conuter를 담당하는 reducer는 counter : counterReducer로 전해진다.
(3). counterReducer를 다른 파일에서 생성해 전해준다.
reduxt toolkit의 새롭고 강력한 slice를 이해해야 reducer를 이해할 수 있다.
// 이전 reducer
const reducer = (prevState, action) => {
switch (action.type) {
case "LOG_IN":
return {
...prevState,
isLogIn: true,
userId: action.data.userId,
data: action.data
};
// toolkit의 slice
export const counterSlice = createSlice({ // 2.
name: 'counter', // 3.
initialState: { // 4.
value: 0,
},
reducers: { // 5.
increment: (state) => {
state.value += 1;
},
toolkit은 이전의 reducer가 action 함수로부터 action 객체를 받아 type 검사를 통해
immutable을 지키며 새 함수를 만들어 내는 방법을 사용했다.
이는 이해하기는 편하지만 코드가 계속 길어지고 객체의 깊이가 깊어질수록 코드는 난해해져 간다는 문제점이
있어 immer라는 라이브러리를 추가하고 보통 3~4개의 라이브러리를 사용하는게 일반적이였다.
slice의 기본적인 아이디어는 이렇다.
logIn reducer는 logIn action creator 함수와 짝이므로 항상 같이다니게 마련이다.
따라서 아예 붙여버린 형태가 slice이다.
이제
configureStore는 counterReducer로 부터 reducer를 받았다.
counterReducer엔 무엇이 들어있나 확인하자.
// ./store/counter.js
import { createSlice } from '@reduxjs/toolkit'; // 1.
export const counterSlice = createSlice({ // 2.
name: 'counter', // 3.
initialState: { // 4.
value: 0,
},
reducers: { // 5.
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload; // 6.
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions; // 7.
export default counterSlice.reducer; // 8.
export const selectCount = (state) => state.counter.value; // 9.
(1). @reduxjs/toolkit으로부터 createSlice를 불러온다.
(2). configureStore에 넘기기 위해 export 해준다.
(3). slice의 이름을 정해준다.
(4). initialState를 정해준다.
(5). reducers 내부에 동기 reducer들이 들어가게 된다. (비동기 처리 reducers는 따로)
reducers의 action의 첫 인자는 prevState가 되고 그 다음부터는 사용자가 입력해서 넣어주는 인자인 action값이다.
(6). incrementByAmount 는 action을 받아 payload값을 더해 넘겨준다.
(7). action에 해당하는 increment들을 export할 때 sliceName.actions로 내보낸다.
(8). slice 자체는 sliceName.reducer로 내보낸다.
store.js는 위 파일을 전부 reducer로 받게된다.
(9). 이 부분은 initialState에서 선언한 state를 export한다,
(state.name.stateName을 export 해주자) 까먹으면 정작 state를 사용하지 못한다. state도 export 해준다.
store를 만들고 export하였으므로 App에서 사용해보자.
redux toolkit이 되었다고 reduxd의 작동 방식이 달라진건 아니다.
원하는 state를 store에 저장하고 dispatch, getState를 통해 가져온다는 기본 구조는 그대로이다.
따라서 위의 increment 버튼의 onClick이벤트는 dispatch는 increment action을
decrement는 decrement action을
5 increment는 incrementByAmount action을 dispatch 해준다.
import React from 'react';
import { useDispatch, useSelector } from 'react-redux'; // 1, 2.
import {
increment,
decrement,
incrementByAmount,
selectCount,
} from '../store/counter';
const App = () => {
const count = useSelector(selectCount); // 2.
console.log(typeof count);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(increment())}>increment</button>
<div>count is {count}</div>
<button onClick={() => dispatch(decrement())}>decrement</button>
<button onClick={() => dispatch(incrementByAmount(5))}>
5 increment
</button>
</div>
);
};
export default App;
(1). useDispatch는 기존의 dispatch와 같다.
(2). useSelector는 store의 저장된 값 중 counter값을 selecte하고 그 값을 반환한다.
확실히 짧아졌지만 기존의 redux와도 많이 달라졌기에 적응하는데 시간이 걸린다.