RTK의 핵심은 리덕스의 복잡함을 낮추고 사용성을 개선하는 것입니다.
그럼 리덕스는 왜 복잡하다고 생각할까요?
npm install @reduxjs/toolkit //npm
yarn add @reduxjs/toolkit //yarn
redux와 같이 store를 생성해 줍니다.
configureStore를 통해 reducer를 정의해 줍니다.
또한 store.getState를 통해서 state의 값을 가져옵니다.
import { configureStore } from '@reduxjs/toolkit'
// ...
const store = configureStore({
reducer: {
one: oneSlice.reducer,
two: twoSlice.reducer,
},
})
//state
export type RootState = ReturnType<typeof store.getState>
//dispatch
export const useAppDispatch: () => AppDispatch = useDispatch
export default store
createSlice를 통해서 reducer를 간단하게 정의할 수 있습니다.
간단한 counter 예제를 통해 알아보겠습니다.
createSlice 안에는 name, initialState, reducers로 구성될 수 있습니다.
RTK의 장점에서 봤듯이 immer가 내장되어 있기 때문에 불변성을 신경 쓰지 않고 코드를 작성할 수 있습니다.
import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from './store';
//initialState의 타입
interface CounterState {
count: number;
countExtra: number;
}
const initialState: CounterState = {
count: 0,
countExtra: 0,
};
//createSlice
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increase: (state) => {
state.count += 1;
},
decrease: (state) => {
state.count -= 1;
},
increaseBy: (state, action: PayloadAction<number>) => {
state.count += action.payload;
},
},
});
export const { increase, decrease, increaseBy } = counterSlice.actions;
export default counterSlice.reducer;
createSelector는 우리가 알고 있는 Reselect와 동일합니다.
먼저 살펴보기 전에 기존에 사용해왔던 useSelector의 문제점을 알아봅시다.
useSelector는 store에서 필요한 state를 가져와서 사용하기 위해 사용하는 함수이다.
이렇기 때문에 createSelector는 현재의 state 값을 memoization을 하여 state의 값이 변하지 않는다면 이전에 저장되었던 값을 불러오는 방식으로 불필요한 re-rendering을 막을 수 있다.
import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from './store';
//initialState의 타입
interface CounterState {
count: number;
countExtra: number;
}
const initialState: CounterState = {
count: 0,
countExtra: 0,
};
//createSlice
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increase: (state) => {
state.count += 1;
},
decrease: (state) => {
state.count -= 1;
},
increaseBy: (state, action: PayloadAction<number>) => {
state.count += action.payload;
},
},
});
//createSelector
//count의 값만 참조하는 selector 반환
const countSelector = (state: RootState): number => {
return state.counter.count | initialState.count;
};
//countExtra값만 참조하는 selector 반환
const countExtraSelector = (state: RootState): number => {
return state.counter.countExtra | initialState.countExtra;
};
//위에 정의한 2개의 함수로 createSelector 생성
export const counterSelector = createSelector(
countSelector,
countExtraSelector,
(count, countExtra) => ({
count,
countExtra,
}),
);
export const { increase, decrease, increaseBy } = counterSlice.actions;
export default counterSlice.reducer;
무엇보다도 코드의 양을 줄일 수 있다는 것이 굉장한 장점인 것 같다.
매번 액션 타입, 액션 생성 함수, 리듀스를 작성하는 것에 피로감을 느끼고 있었기 때문에,,
아직 모든 기능을 사용해 보지는 않았지만 다른 많은 기능이 있기 때문에 공식 문서를 통해 알아보는 것이 중요할 거 같다.