기존에 redux의 ducks pattern을 사용해서 상태를 관리했었는데, 타이핑할 코드가 굉장히 길어서 불편했었다. 그런 불편을 해소하기 위해 나온 것이 바로 redux toolkit
이다.
// src/redux/modules/counterSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
number: 0,
};
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
addNumber: (state, action) => {
state.number = state.number + action.payload;
},
minusNumber: (state, action) => {
state.number = state.number - action.payload;
},
},
});
// 액션크리에이터는 컴포넌트에서 사용하기 위해 export 하고
export const { addNumber, minusNumber } = counterSlice.actions;
// reducer 는 configStore에 등록하기 위해 export default 합니다.
export default counterSlice.reducer;
내가 만든 counterSlice
는 액션객체와 리듀서 함수 모두를 가지고 있는 객체다. 얘는 모듈이라고 불러도 되고 redux toolkit 문서에서는 slice
라는 이름으로 부르기도 한다.
counterSlice는 createSlice
라는 api를 사용해서 만들어낸 객체인데, 거기에는 꼭 반드시 reducers
라는 키 값으로 내가 만들고 싶은 액션객체와 리듀서 함수를 담아야한다. 이거는 그냥 문법적 약속인거임
createSlice() 함수에서는 reducers라는 key를 사용하여 리듀서 함수를 정의합니다. 이것은 Redux Toolkit이 제공하는 문법 구조입니다. 따라서 reducers라는 key가 없는 경우, createSlice() 함수에서는 TypeError를 발생시킵니다.
reducers: {
addNumber: (state, action) => {
state.number = state.number + action.payload;
},
내가 이렇게 reducers라는 키 값에 만든 객체에는 action creator와 reducer 함수가 포함된거다. why? 걍 redux toolkit의 createSlice api가 그걸 해주는거임.
내가 {action creator name : reducer 함수 }
이렇게 작성하면
알아서 action creator에 대한 type도 만들고, 그 type일 때 실행할 reducer 함수도 만드는거임.
그리고 사용할 때는 반드시 counterSlice.actions
coutnerSlice.reducer
로 export 하면되는데, actions랑 reducer라는 이름도 toolkit 문법적 약속임. 다른 이름으로 접근하면 안됨
createSlice() 함수는 자동으로 각각의 액션 생성 함수에 대한 type 속성을 생성하므로, counterSlice.actions를 호출하면 이 객체에 접근할 수 있습니다. 예를 들어, addNumber() 액션 생성 함수의 type은 "counter/addNumber"입니다.
yarn add @reduxjs/toolkit
위의 명령어를 사용해서 redux toolkit을 설치해준다.
// 일반 리덕스 combineReducers 코드
const rootReducer = combineReducers({
counter,
});
const store = createStore(rootReducer);
export default store;
기존의 redux로 관리했던 configStore.js는 위와 같다.
combineReducers와 createStore를 사용해서 store를 정의했지만
redux toolkit을 사용하면 아래와 같이 간결하다.
// redux toolkit 사용 코드
import counter from "../modules/counterSlice";
import todos from "../modules/todosSlice";
const store = configureStore({
reducer: { counter: counter, todos: todos },
});
export default store;
따라서 configureStore api로 여러 모듈(slice)를 하나의 reducer key에 담은 store 객체를 생성하고 이것을 export하면 된다.
여러 slice를 하나의 store로 저장하는 작업을 2번에서 진행했다. 이때 기존의 모듈을 합쳐주는 작업을 configureStore
api를 사용해서 진행했다.
그렇다면 나는 컴포넌트에서 내가 만든 모듈(slice)를 어떻게 사용해야 할까?
나는 처음에 아래와 같이 생각했다.
const counterState = useSelector((state)=> state.reducer.counter
하지만 아니다!
결론부터 말하자면
state.reducer.counter와 같이 reducer를 명시할 필요는 없습니다. 그 이유는 Redux Toolkit에서 configureStore() 함수를 사용하면, reducer 객체가 루트 리듀서 함수에 대한 여러 Slice 리듀서 함수를 하나로 합치는 작업을 수행하기 때문입니다. 따라서, configureStore() 함수를 사용하면 reducer 객체의 key 값으로 각각의 Slice 리듀서 함수를 등록하고, 이들을 하나의 루트 리듀서 함수로 합친 다음에, store에 등록합니다.
위와 같은 이유로 state.reducer.counter
가 아닌 아래의 코드로 모듈을 사용할 수 있다.
const counterState = useSelector((state) => state.counter);
이후에 dispatch로 내가 export한 action creators를 사용하는 것은 redux 방식과 동일하다.