3. Redux-ToolKit 의 이론과 기본 사용
본격적으로 Redux(Tool-kit) 를 다루어보자
1. Redux-ToolKit
Redux 의 개발사가 만든, 상위 버젼의 Redux 이다.
2. 기본 베이스
사용 원리는 동일하다.
1) 리듀서 함수를 통해, State 를 조작한다.
2) "하나" 의 저장소에 모든 State 를 담는다.
* 컴포넌트에서 사용하거나, Provider 하는 역할은 "react-redux" 가 하는 역할이므로, 생략한다.
* "react-redux" 는 똑같이 사용된다. 우리는 리덕스를 교체한것이다.
3. 그럼 뭐가 다를까?
1) Reducer Funciton 이 아니라, createSlice 를 통해, "Slice" 를 생성한다.
2) createStore 가 아니라, configureStore 를 통해, "저장소"를 만든다.
3 ) 원본객체를 변형하는 "척" 을 할 수 있다. 마음대로 원본객체를 변경해도,
툴킷이 알아서, 복사하여, 원본객체변형이 일어나지 않게 만들어준다.
코드를 통해 자세하게 알아보자.
Redux-TookKit 의 장점1 : createSlice 를 통한 코드의 가독성 및 안정화 [ reducerFn 의 교체 ]
import { createStore } from "redux" // 일단 내비두었다. 곧 새걸로 교체될것이다.
import { createSlice } from "@reduxjs/toolkit";
const initialState = {counter :0, showCounter: false}
const counterSlice = createSlice({
name: "counter", // 1. reducerFn이 함수이름을 갖듯, "name 은 필수로 지정해야한다."
initialState : initialState, // 2. "초기값도 반드시 지정해주도록한다."
reducers :{ // 3. reducers 프로퍼티에, 리듀서함수를 넣어주면된다.
incremen(state){ // 4. 이때의 state는 항상 최신 값을 반환한다.
state.counter++ // 5. 원본에 직접 손을 대도, toolkit안의 다른 패키지가, 이것을 복사하여 사용하게 만든다. (안정화 = 원본 절대 변경 불가)
} // (return 할 필요도 없다. 당연히 모든 value 에 대하여, 사라지지 않게 하기 위한 return 도 필요 없다)
decerment(state){
state.counter--
}
increase(state, action){ // 6. action은 필요한 경우에만 써도 된다.
state.counter = state.counter + action.payload // 7. action.payload 에 기본적으로 action 으로 온 값이 저장된다.
}
toggleCounter(state){
state.showCounter = !state.showCounter
}
}
})
const store = createStore(counterSlice.reducer) // 8. 이제, store 에 createSlice로 만든 슬라이스의 reducers 를 넣어준다.
export default store
변경된 점을 살펴보자.
1. createSlice 를 통해, 기존 reducerFn 을 교체하였다.
2. createSlice 의 옵션으로, name ,initialState, reducers 가 존재한다.
3. reducers 안에, action 들이 들어가고, action 은 "함수 타입" 으로 생성된다.
4. *** 원본을 변경 할 수 있다. 해도 리덕스 툴킷은 자동으로, 복사본을 생성하고, 오버라이드 한다.
5. *** 모든 State 를 전부 꺼내 return 할 필요가 없다. 바뀌는 value 들만 리덕스툴킷이 변경해준다.
6. store 를 만들때, Slice 안의 reducer 만을 빼, export한다. ( 단수 reducer 임을 명심)
저장소를 만들때, Slice 안의 reducer "s" 가 아니고 reducer 인 이유
** "reducers 가 아니고" reducer 가 "단수" 인 이유
createStore 로 만들던, configureStore 로 만들던, "리덕스가 사용할(관리할) reducerFn(Slice)"은 "반드시 하나여야한다"
** 그럼 왜? configureStore 를 굳이 사용하는가?
configureStore 는 "여러개의 Slice를 동시에 적용할 수 있다."
** 반드시 하나라고 했는데!
configureStore 는 "여러개의 Slice 를 객체형태로 key 와 함께 넣으면, 알아서 merge 하여 하나롬 만들어준다."
Redux-TookKit 의 장점2 : configureStore 를 통한, "여러 리듀서 통합" [ createStore 의 교체 ]
import { createSlice, configureStore } from "@reduxjs/toolkit"; // 1. configureStore 로 교체
const initialState = {counter :0, showCounter: false}
const counterSlice = createSlice({
name: "counter",
initialState,
reducers :{
...
}
...
const store = configureStore({ // 2. 이제, store 에 createSlice로 만든 슬라이스의 reducer 를 넣어준다.
reducer : counterSlice.reducer // 3. configureStore 에 들어가는 객체의 프로퍼티는 "reducer" 단수이다.
})
export default store
변경된 점을 살펴보자.
1. configureStore 로 저장소를 만든다.
2. 객체를 생성해, reducer 라는 프로퍼티 안에, Slice 의 reducer 를 넣어 export 한다.
앞서 궁금했던 여러 Slice 를 동시에 다루는 것을 미리 살짝 맛보아보자.
const CounterSlice = createSlice({
name: ...,
initialState : ...,
reducers : {
...
}
})
const newCounterSlice = createSlice({
name: ...,
initialState : ...,
reducers : {
...
}
})
const store = configureStore({
reducer : { counter : CounterSlice.reduce, newCounter : newCounterSlice.reduce }
})
export default store
Redux-TookKit 의 장점3 : createSlice 와 액션식별자의 사용으로, 오타를 방지
앞서 createSlice 에는 기존 ReducerFn 과는 다르게, "함수형" 의 액션들이 지정되었다.
기존 Redux 는, 컴포넌트에, react-redux 로 연결후, dispatch 를 사용할때, 그 "액션타입"을 수기로 작성했다.
하지만! Redux-TookKit 은, "액션생성자 메서드" 를 통해, "액션을 생성" 할 것이다.
최종적으로 Store 를 완성해보자
< store / index.js >
import { createStore } from "redux" // 일단 내비두었다. 곧 새걸로 교체될것이다.
import { createSlice } from "@reduxjs/toolkit";
const initialState = {counter :0, showCounter: false}
const counterSlice = createSlice({
name: "counter",
initialState : initialState,
reducers :{
incremen(state){
state.counter++
...
toggleCounter(state){
state.showCounter = !state.showCounter
}
}
})
export const counterAction = counterSlice.actions // actions 옵션을 통해, action 객체에 접근한다.
const store = createStore(counterSlice.reducer)
export default store
** "Slice의 actions 에 접근한 counterSlice.actions을 export 시킨다."
** "Slice의 actions 에 접근한 counterSlice.actions을 받아, reducer 를 사용한다."
< 컴포넌트 >
import { useSelector, useDispatch } from "react-redux";
import { counterAction } from "../store/index"; // counterAction 을 import 한다.
const Counter = () => {
const counter = useSelector((state) => state.counter); // counterState 를 가져올 수 있다.
const showCounter = useSelector((state) => state.showCounter); // showCounterState 를 가져 올 수 있다.
const dispatch = useDispatch();
const toggleCounterHandler = () => {
dispatch(counterAction.toggleCounter()); // counterAction 을 통해, Action 을 자동완성한다.
};
...
};
const userinputHandler = () => {
dispatch(counterAction.userinput(5)); // ** payload 에 관하여
};
return (
...
)
** export 한 counterAction 을 사용하면 "자동완성" 으로 Action 들이 생성된다.
** 오타 없이 간단하게, action 을 안정적으로 사용 가능하다.
** 페이로드 ( payload ) 에 관하여.
@reduxjs/toolkit 은 ,action 을 받아올때, 기본적으로 "payload 객체" 안에 저장되어 받아온다.
[컴포넌트]
const userinputHandler = () => {
dispatch(counterAction.userinput(5)); // ** payload 에 관하여
};
이렇게 사용했다면
[리듀서]
payload = {5} 처럼 받아 오는 것이다. (key는 모르겠다)
"다른 예시로"
[컴포넌트]
const userinputHandler = () => {
dispatch(counterAction.userinput({inputNum : 5, myNum : 10 }));
};
이렇게 사용했다면
[리듀서]
payload = { inputNum : 5, myNum : 10 }
[만약 inputNum 과 myNum 을 각각 사용하는 Slice 라면 ]
increase(){
state.counter = state.counter + action.payload.inputNum + action.payload.myNum
}
<정리>
"기본적으로 reducer 를 만들때, action 파라미터의 인수"로 "payload" 프로퍼티를 받은것이다.
userinput(state, action) {
state.counter = state.counter + action.payload; // "payload" 프로퍼티를 받은것이다.
},