리덕스의 핵심은 중앙 저장소인 store에서 상태를 효율적으로 관리하여 각 컴포넌트로 바로 전달하는 것이다.
우리가 만드는 컴포넌트에는 공통적으로 사용되는 데이터가 존재한다. 만약 이런 공통 데이터를 한곳에서 관리하지 않는다면, 부모 컴포넌트에서 자식 컴포넌트로 데이터를 주고받는 과정에서 관리가 복잡해지면서 문제가 발생할 수 있다. 규모가 작은 프로젝트라면 큰 문제가 되지 않겠지만 서비스 규모가 커지고 공통으로 관리하는 데이터와 컴포넌트가 많아진다면 복잡성은 더욱 커질 것이다. 그렇기 때문에 상태 관리를 위한 라이브러리의 필요성을 느꼈다.
리덕스 외에도 다른 상태 관리 라이브러리나 context API와 같이 자체 기능도 존재한다. 하지만 성능 최적화나 비동기 지원 난이도의 측면에서 context API 보다 redux가 앞선다고 판단했고, 리덕스 다음으로 많이 사용되는 상태 관리 라이브러리인 mobx는 코드량은 줄지만, Redux DevTools과 같은 디버깅 도구가 없어 실수가 있을 때 트래킹이 어렵다는 단점이 있었다. redux는 디버깅 툴로 action이 하나하나 기록되어 추적이 가능하기 때문에 에러의 해결이 용이하다. 작은 프로젝트라면 mobx를 사용해도 괜찮겠지만 복잡한 상태를 관리하기엔 결론적으로 redux가 더 적합하다고 생각했다.
그리고 리덕스에서 TypeScript를 사용하지 않으면 컴포넌트에서 상태를 조회할때나 액션생성 함수를 사용 할 때 자동완성이 되지 않으므로 실수하기가 쉬운데, 우리 프로젝트는 TypeScript를 도입하고 있기 때문에 일일이 찾아보지 않고 자동완성으로 찾을 수 있다는 장점이 있었다.
또한 보일러 플레이트 코드가 많아지는 단점의 경우, 리덕스 개발팀에서 릴리즈한 Redux Toolkit이라는 라이브러리가 이같은 문제를 포함한 기존 리덕스가 가진 여러 문제를 해결하고 있다는 것을 알게 되었다. 따라서 리덕스를 더 쉽게 사용할 수 있게 도와주는 Redux Toolkit을 사용해보기로 결정했다.
리덕스는 기본적으로 액션 타입, 액션 생성함수, 리듀서를 정의해야 하기 때문에 많은 보일러플레이트 코드를 작성할 수밖에 없다. 또 코드 단순화를 위해 redux-actions을, typescript 지원을 위해 typesafe-actions, 불변성 간소화 코드 작성을 위해 immer 등등의 라이브러리를 별도로 도입해야 했다. 리덕스 툴킷은 이런 문제를 해결하기 위해 리덕스 개발팀에서 공식적으로 제공하는 라이브러리이다.
공식문서에서 말하는 리덕스 툴킷의 특징은 다음과 같다.
Simple
스토어 설정, 리듀서 생성, 변경 불가능한 업데이트 로직 등과 같은 일반적인 사용 사례를 단순화하는 유틸리티 포함
Opinionated: 스토어 설정을 위한 좋은 기본값 제공, 가장 일반적으로 사용되는 Redux addons 내장
Powerful: Immer 및 Autodux와 같은 라이브러리에서 영감을 얻어 mutative하게 작성해도 불변성 로직으로 작성 가능, 전체 상태 슬라이스를 자동으로 생성 가능
Effective: 적은 코드로 많은 작업 수행 가능
이 특징을 확인하기 위해 먼저 리덕스 툴킷을 적용하지 않았을 때 코드를 살펴보자.
export const OPEN = 'msgbox/OPEN';
export const CLOSE = 'msgbox/CLOSE';
export const open = (message) => ({ type: OPEN, message });
const initialState = {
open: false,
message: '',
};
export default msgbox(state = initialState, action) {
switch (action.type) {
case OPEN:
return { ...state, open: true, message: action.message };
case CLOSE:
return { ...state, open: false };
default:
return state;
}
}
기존 리덕스 환경에선 위와 같은 방식을 기본으로 작업해야 하기 때문에 규모가 커질 경우 많은 코드를 작성해야 하고, 상태의 불변성 유지를 위한 ...state 사용의 번거로움이 따라왔다.
이제 Redux Toolkit을 사용한다면 리듀서, 액션타입, 액션 생성함수, 초기상태를 하나의 함수로 선언할 수 있다. 이 4가지를 통틀어 slice
라고 부른다.
import { createSlice } from '@reduxjs/toolkit';
const msgboxSlice = createSlice({
name: 'msgbox',
initialState: {
open: false,
message: '',
},
reducers: {
open(state, action) {
state.open = true;
state.message = action.payload
},
close(state) {
state.open = false;
}
}
});
export default msgboxSlice;
리덕스 툴킷에 내장된 createSlice
기능을 사용하면 위와 같이 리듀서와 액션 생성 함수를 한번에 만드는 게 가능하다. 그리고 Immer
가 내장되어있기 때문에, 불변성을 유지하기 위해 번거로운 코드를 작성하지 않아도 원하는 값을 직접 변경하면 알아서 불변성이 유지되면서 상태가 업데이트 된다.
이전에 리덕스를 사용해보며 만족했던 부분과 아쉬웠던 부분이 동시에 존재했다. 그런데 리덕스 툴킷을 학습하면서 확실히 이 녀석이 그 아쉬웠던 부분을 채워줄 수 있을 것 같다는 생각이 들어 기쁜 마음으로 함께 하게 되었다😎
- References
리덕스, 어떻게 해야 잘 쓸까?
Redux-Toolkit 공식문서