다들 상태관리에 대해서 어떻게 생각하시나요?
정말 간단하게 말하면 변화하는 데이터를 관리 즉, 개발자 입장에서 문자열, 배열, 객체 등의 형태로 저장된 데이터들을 관리해야 하는 것을 상태관리라고 요약할 수 있겠습니다.
그러면 다들 서버 상태관리 라이브러리로 어떤걸 사용하고 계신가요?
프로덕트가 커짐에 따라서 상태를 관리하기에 어려움도 커지고, 상태들은 시간에 따라서 계속 변화하고 React는 단방향 바인딩이므로 Props Drilling 이슈도 존재합니다.
이에 따라 저희도 요즘 상태관리에 대한 고민이 되게 많았습니다.
- 기능추가를 할수록 계속해서 커지는 Store
- API 통신 관련 코드를 작성하는데 반복되는 코드작업과 너무 긴 코드 라인 수
- 비효율적이게 지속적으로 호출되는 API
그렇다고 저희 클라이언트 규모가 엄청 크거나 그렇진 않습니다.
오히려 규모가 작을 때 전환의 대한 추진을 해보는 것이 좋지 않을까?라는 생각과 추가적으로 Redux-saga를 react-query로 도입하면서 생기는 이점들에 대해서 정리해 보았습니다.
- 효율적인 데이터 캐싱 (서버에서 변경될 가능성이 낮은 API들의 대해서 추가 호출 방지)
- 코드의 라인수가 줄어듦에 있어 코드 가독성이 좋아짐 (각 컴포넌트 내 코드 단순화 + 가독성)
- 새로운 기능 및 유지보수 측면에서 리소스가 줄어듦
- 결합도가 높았던 로직들을 분리하여 재사용성 증가
- Error Flag 및 Loding과 Fetching의 효율적 대응 가능
더 있겠지만 크게 이 4가지로 정리할 수 있었습니다.
마지막으로 이건 개인적이지만 제가 Docs와 블로그를 읽어보면서 너무나 써보고 싶었던 라이브러리 입니다.
이 Redux-saga 코드들은 전임자분께서 작성하셨던 코드들이였고 저는 그걸 추가 및 보수만 하고 있었는데 이참에 걷어낼 건 걷어내고 react-query로 전환하면서 효율적으로 바꿔보자라는 결심에 이런 리팩토링의 대한 이점들을 정리하여 보고 후에 리팩터링 할 시간을 얻어냈습니다..!
저희 클라이언트는 기존 Redux-Saga로 서버 상태를 관리하고 있었습니다.
// redux-saga
import { call, put, takeLatest } from "redux-saga/effects";
// api
// util
import { handleError } from "utils/error";
import { patchAddressInformation, patchMyinfoPersonalInformation, getMyinfo, putMyinfoSuitaility } from "api/myinfo";
import { investSuccessAction } from "./invest";
import { getInvestAndMyxquare } from "@api";
// Types
const MYINFO_LOADING = "MYINFO_LOADING";
const MYINFO_SUCCESS = "MYINFO_SUCCESS";
const MYINFO_ERROR = "MYINFO_ERROR";
const GET_MYINFO = "GET_MYINFO";
// Action Function
const myinfoLoadingAction = data => ({ type: MYINFO_LOADING, payload: data });
const myinfoSuccessAction = data => ({ type: MYINFO_SUCCESS, payload: data });
const myinfoFailAction = data => ({ type: MYINFO_ERROR, payload: data });
export const getMyinfoAction = () => ({ type: GET_MYINFO });
// Saga Function
function* getMyinfoSaga() {
try {
yield put(myinfoLoadingAction());
const result = yield call(getMyinfo);
yield put(myinfoSuccessAction(result));
} catch (err) {
yield put(myinfoFailAction(handleError(err)));
}
}
export function* myinfoSaga() {
yield takeLatest(GET_MYINFO, getMyinfoSaga);
}
// InitialState
const initialState = {
loading: false,
data: null,
error: null,
};
// Reducer
export default function myinfo(state = initialState, action) {
switch (action.type) {
case MYINFO_LOADING:
return {
loading: true,
data: null,
error: null,
};
case MYINFO_SUCCESS:
return {
loading: false,
data: action.payload.data,
error: null,
};
case MYINFO_ERROR:
return {
loading: false,
data: null,
error: action.payload,
};
default:
return state;
}
}
물론 예제 코드기 때문에 get밖에 없지만 여기에 post, put, patch 여러가지 api가 추가되면 타입 지정, 액션, 사가 등 여러가지를 추가해줘야 합니다. 또 get 하나를 위해서 써줘야 하는 코드가 상당히 길죠..
여기서 또 하나의 의문점이 듭니다. 이 코드를 보면서 스토어라는 역할보다는 API 통신이 주된 역할로 보이지 않으시나요?
그럼 API 통신 관련 코드를 더 간단히 구현할 수 없을까?
똑같이 get을 위한 React-Query로 작성한 코드를 보겠습니다.
export const useGetMyInfo = () => {
const { data, refetch, error } = useQuery("myInfo", getMyinfo);
const myInfoResponse = data?.data;
return { myInfoResponse, refetch, error };
};
정말 간결하지 않나요? query-key를 통하여 데이터 캐싱을 할 수 있고, isLoading, isFetching을 통하여 많은 것을 제어할 수도 있습니다.
더 자세히 살펴보시려면 https://react-query.tanstack.com/overview 리액트 쿼리 공식문서 사이트를 참고하시는 것을 추천드립니다.
이렇게 모든 Redux-saga를 떼어내고 React-query로 리팩터링을 진행하고 있습니다.
그렇다고 Redux를 떼어내는건 아닙니다. 클라이언트 상태관리는 그대로 Redux로 관리하고 서버 상태만 Redux-saga에서 React-query로 옮기는 것이니까요. ㅎㅎ
다음 포스트엔 아마 React-query로 리팩터링을 진행하면서 얻은 것들을 정리해서 포스트 해보려고 합니다.
언제든 의견은 환영합니다!!!
참고 블로그
https://techblog.woowahan.com/6339/
'개발일지' 카테고리의 다른 글
타입스크립트로 마이그레이션 여정기 1 (0) | 2022.04.30 |
---|---|
redux-saga에서 react-query로 전환하기 2 (0) | 2022.03.02 |
2021년 회고 (0) | 2022.01.01 |
Admin 회고 (0) | 2021.06.05 |
응집도와 결합도 (0) | 2021.06.05 |