React
1
npm i redux react-redux @reduxjs/toolkit
Redux-Toolkit (RTK)
- Next.js에서 Redux-Toolkit을 사용하기 전에 자세히 알아보자
- 기존의 리덕스를 사용 시, 상태 관리를 할수록 코드가 많아지고 복잡해지는 문제점을 해결한 것이다.
Redux vs Redux-Toolkit
Redux
reducer를 구현할 때마다type이름을 적어야하며,switch/if문을 반복적으로 사용해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// store.js
import { createStore } from 'redux';
const reducer = (state = { counter: 0 }, action) => {
switch(action.type) {
case 'increment':
return { counter: state.counter + 1 }
case 'decrement':
return { counter: state.counter - 1 }
default:
return state
}
}
const store = createStore(reducer);
export default store;
// App.jsx
import { useSelector, useDispatch } from 'react-redux'
function App() {
const counter = useSelector((state) => state.counter);
const dispatch = useDispatch();
const increment = () => {
dispatch({ type: 'increment' })
}
const decrement = () => {
dispatch({ type: 'decrement' })
}
// ...
}
export default App
Redux-Toolkit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// store/counterSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
value: 0,
};
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
// store.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counterSlice";
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
// App.jsx
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { decrement, increment } from "./store/counterSlice";
export function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
);
}
Dispatch
dispatch를 할 때마다action type / payload를 작성해야 되던 것과 달리 slice에서 작성한reducer을 바로 불러와서 사용이 가능하다.
1
2
3
4
5
// redux
dispatch({type: Actions.Add, payload: {value: ...} })
// RTK
dispatch(add(...))
Store
- 기존
createStore가 아닌configureStore를 사용한다. redux-thunk와devtools를 기본으로 제공하며,reducer에서 반환된 새로운 상태를Store에서 관리한다.createStore와 비슷하지만{reducer: ...}로 작성해야 한다.
1
2
3
4
5
6
7
8
// redux
const store = createStore(reducer);
// RTK
const store = configureStore({
reducer: reducer,
middleWare: (getDefaultMiddleware) => ...
})
Reducer
- 기존의 상태관리와는 달리 mutating 으로 작성하는 것이 가능하다.
immer라이브러리를 사용하고 있기 때문에 실제로는 기존의 상태를 변형하는 것이 아니라, 기존 상태에 대한 변경을 감지하고 변경 상태를 바탕으로 새로운 immutable state를 생성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// redux
const reducer = (state, action) = {
switch(action.type){
case "increment" :
return {...state, number: action.payload...}
}
}
// RTK
const exampleReducerSlice = createReducer({
name: "",
initialState,
reducers:{
increment: (state, action) => {
state.value = action.payload...
}
}
})