앞으로 진행하게 될 협업 프로젝트에 redux-toolkit
을 적용할 것 같아 익히고 정리하는 시간을 가지려고 합니다. 기존에 redux
가 여러가지 문제점을 갖고 있었는데, 문제점으로는 1) 저장소 구성 시 복잡함, 2) 외부 패키지에 대한 높은 의존성, 3) 한 작업 시 필요한 많은 코드양 등이 있습니다. 이에 대한 보완책으로 redux-toolkit
이라는 것이 나오게 되었습니다.
특히, 리덕스를 라이브러리 없이 사용 시 1개의 액션을 생성해도 1) 액션타입 정의 -> 2) 액션함수 생성 -> 3) reducer 정의와 같은 단계가 무조건적으로 필요했습니다.
하지만,
redux-toolkit
을 활용하게 되면, action creator를 생성해주지 않아도 자동으로 생성해주는 특징이 있어 코드 양도 줄고, 가독성도 높아지는 장점이 있습니다.
그럼, 기본적인 개념과 redux toolkit이 갖고 있는 유틸리티 함수들을 어떻게 활용하는 지 정리해보는 시간을 가져보겠습니다.
createSlice
는 redux가 갖고 있던 방대하게 커질 가능성이 있는 store
를 기능별로 분류한 몇 개의 store로 분리하는 store의 조각 모음이라고 생각하면 되겠습니다.
기존에 redux에서는 action 타입을 정의하고, action creator(액션 함수)를 생성하고, reducer를 정의하는 과정을 거쳤다면, createSlice
를 활용하면 action + reducer 모두를 가진 함수로 한 번에 작성 및 사용가능 합니다.
기본적인 구조를 살펴보겠습니다.
interface InitialValueType {
value: number
}
const initialValue: InitialValueType = {value : 0};
const counterSlice = createSlice({
name: 'counterSlice',
initialValue,
reducers: {
plus: (state, action: PayloadAction<number>) => {
state.value = state.value + action.payload;
},
minus: (state, action: PayloadAction<number>) => {
state.value = state.value - action.payload;
}
})
export const { plus, minus } = counterSlice.actions
export const counterReducer = counterSlice.reducer
reducers
에는plus
는action Type
|(state, action) => { ... }
는reducer
함수 | 그리고 생략된action 생성 함수
가 합쳐진 형태라고 생각하면 됩니다.
마지막 export 코드는 바로 다음에 다뤄질 configureStore
에서 쉽게 우리가 사용할 reducer들을 담기 위해서 reducer들을 하나의 변수에 담아준 코드입니다.
configureStore
는 전역 상태를 변경시키는 로직인 reducer
를 담은 store
를 저장하는 전체 보관소라고 생각하면 됩니다.
기본적인 구조는 다음과 같습니다.
const store = configureStore({
reducer: {
counter: counterReducer
// 더 많은 `reducerName: reducer` 모음 추가가능
}
})
기존의 redux와 달리 combineReducers
와 같은 함수로 reducer
들을 묶어줄 필요없이, reducer
필드를 넣고 그 안에 객체(key - value)의 형태로 key에 해당 reducer들을 공통으로 묶어줄 이름으로 명명하고, value 자리에는 위의 createSlice 함수를 볼 때 마지막 코드에서 export 했던 couterReducer
를 담아주면 됩니다.
우리가 일반적으로 React에서 contextAPI
를 활용하면 전역 상태 관리가 가능해지는데, 이 때 Provider
를 활용하여 전역 상태 관리가 될 대상을 props로 넘겨주었습니다. 같은 맥락으로 Provider
컴포넌트를 활용해서 store
를 넘겨주면, 전역으로 우리가 관리할 상태를 dispatch
를 통해 변경하거나 수정할 수 있습니다.
코드를 살펴보겠습니다.
// App.tsx
import { Provider } from 'react-redux'
import { store } from './store/store.ts'
import Counter from './components/Counter'
function App():JSX.Element{
return (
<Provider store={store}>
<Counter/>
</Provider>
)
}
function Counter():JSX.Element{
const dispatch = useDispatch();
const count = useSelector(state => {
const {counter} = state;
return counter.value
});
return (
<div style={{marginTop: '20px'}}>
<div>{count}</div>
<button onClick={() => dispatch({type:'counterSlice/plus', payload: 2})}>plus</button>
<button onClick={() => dispatch({type:'counterSlice/minus', payload: 2})}>minus</button>
</div>
);
}
export default Counter
useSelector
useSelector 내부에 담기는 함수를 보면, 이 함수는 state
를 인자로 받고 state.value로 바로 값에 바로 접근 하는 것이 아니라, 우리가 configureStore
를 활용해 여러 조각으로 나뉜 reducer들을 합치는 저장소를 만들 때, reducer
라는 필드 안에 또, 공통된 속성을 띄는 필드로 key값을 정하고, value에 createSlice
로 조각 생성 당시 생성했던 reducer들을 담은 export한 변수를 할당했습니다.
이 때의 key인 필드가 state.counter.value
를 통해 count
라는 변수에 값이 새롭게 할당되어 사용될 수 있습니다.
useDispatch
액션 타입과 action.payload를 받아 dispatch 해주는 함수라고 정리할 수 있습니다.
redux 공식문서를 통해 redux를 처음 접할 때, 다뤄야 할 개념이나 함수들이 많고, 러닝커브가 너무 큰 것 같아 배우기 당황스러워, 상대적으로 구조가 비슷한 recoil을 통한 전역 상태 관리를 익혔습니다. 하지만, 이번에 redux에 대한 요구가 생겨, redux-toolkit도 조금 더 익히고 프로젝트에 적용해 본다면, 길지 않은 코드로 전역 상태 관리에 있어 유용하게 활용할 수 있을 것 같습니다. 한 가지 기술에만 국한되지 않고, 여러가지 기술들을 경험하다보면, 장단점을 파악하여 어떤 프로젝트에 더 적합할 지 판단 가능해질 것이라고 기대됩니다.
reference
ㄴ 생활코딩의 redux toolkit 강의를 듣고, 이해한 바로 정리하였습니다.