(이전 글에서 계속됩니다)
단계별 적용하기
1. 로직 분류
가장 먼저, 기존에 Controller에 있던 함수들을 전부 분류하는 작업을 진행하였다. 재사용성이 큰 상태 데이터는 Context로, 특정 컴포넌트 내부에서만 사용되는 상태 데이터는 해당 컴포넌트의 Controller로, LogData의 값을 변경하는 데이터는 Reducer로 주석을 달아 분류해주었다.
2. ContextAPI, useReducer 적용
다음 Context를 생성하고 적용을 원하는 범위에 Provider를 감싸주었다. 재사용성이 큰 로직들을 LogContext에 담고, LogData 상태를 변경하는 로직들의 경우 아래의 예시와 같이 객체 형태의 Reducer로 분리하여 Conetxt에서 호출 및 가공하여 사용하도록 했다. useReducer 훅은 이번 작업에서 처음 사용해봤어서, 액션 타입을 지정하는 데에 애를 먹었다.
Reducer
case LOG_RECORD_REDUCER_ACTIONS.REMOVE_PIN: {
const { pinIndex } = action.payload;
return {
...state,
pins: state.pins.filter((_, index) => index !== pinIndex),
};
}
Context
const removePin = (pinIndex: number) => {
dispatch({ type: LOG_RECORD_REDUCER_ACTIONS.REMOVE_PIN, payload: { pinIndex } });
setCurrentPinIndex(-1);
};
3. Controller 커스텀훅으로 변경
기존의 JSX 형태를 갖는 View를 반환하는 방식에서, ContextAPI를 통해 불러올 수 있는 상태를 제외한 값들만 커스텀 훅 형태로 호출 가능하게 변경해주었다. 각 컴포넌트 단계에서 상태를 선언하였던 Model 또한 Controller에 통합시켰다. 이를 통해 컴포넌트 간 전달되는 프롭을 줄이고 결합도를 낮출 수 있었다.
전체적인 개선 과정은 🔗이 곳에서 확인할 수 있다.
결과
1. Prop Drilling 개선
한 예로 최상위 컴포넌트 LogRecord의 View 로직이다. 약 40줄의 코드를 3줄로 줄일 수 있었고, 계단식의 프롭 전달을 거치지 않아도 되고, 별도의 프롭 타입을 지정해주지 않아도 된다. 수정과 확장 측면에서 굉장히 편해졌다.
{pageStep === "LOG_RECORD_STANDBY" && (
<LogRecordStandby
setLogData={setLogData}
setPageStep={setPageStep}
onErrorWatcher={onErrorWatcher}
updateUserLocation={updateUserLocation}
setIsActiveExitAni={setIsActiveExitAni}
/>
)}
{pageStep === "LOG_RECORD_RECORDING" && (
<LogRecordRecording
logData={logData}
setLogData={setLogData}
setPageStep={setPageStep}
onErrorWatcher={onErrorWatcher}
updateUserLocation={updateUserLocation}
setIsActiveExitAni={setIsActiveExitAni}
/>
)}
{pageStep === "LOG_RECORD_EDITING" && (
<LogRecordEdit
logData={logData}
currentPinIndex={currentPinIndex}
setLogData={setLogData}
setCurrentPinIndex={setCurrentPinIndex}
onClickPin={onClickPin}
/>
)}
{pageStep === "LOG_RECORD_STANDBY" && <LogRecordStandby />}
{pageStep === "LOG_RECORD_RECORDING" && <LogRecordRecording />}
{pageStep === "LOG_RECORD_EDITING" && <LogRecordEdit />}
2. 역할에 따라 분류된 로직들
사실 코드 자체는 양이 많다. 그러나 로직의 역할에 맞게 Context, Reducer, Controller 내부에 성격에 맞게 분류하려 노력하였다. 또한 JSDoc을 통한 문서화로 다른 팀원들이 코드를 최대한 쉽게 이해할 수 있게 돕고자 하였다.
문제 발생 원인
왜 이런 문제가 발생했을까? 프로젝트 초기 단계에서 MVC(Model-View-Contorller) 패턴을 적용하여 각 컴포넌트의 역할을 정의하고 설계했다. 해당 패턴을 선택하는데 있어 큰 이유를 두지 않고 다양한 패턴을 경험해보자는 명목 하에, 사이드 이펙트를 고려하지 않은채 작업을 시작한 것이 주된 요인이지 않을까 싶다. 문제점을 발견하고 개선하는 것도 좋지만, 애초에 문제가 발생할 여지를 줄이는게 베스트이지 않을까 생각해본다.
또한 이번에 진행한 리팩토링 작업이 절대적인 정답이란 법은 없다. 문제를 인식하고 다양한 방법론을 적용해보며 개선했다는 부분이 의미가 있지만, 앞으로도 사이드 이펙트를 고려해가며 다양한 패턴과 작업 방식을 경험하고자 한다.