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...
}
}
})