기존 피드백 페이지는 컴포넌트 응집도를 위해 최상단에 비즈니스 로직을 관리하고 상태들을 자식 컴포넌트에 props로 내려주는 형태였습니다. 문제는 비지니스 로직들을 위로 올리면서 피드백 페이지 내에 모든 상태가 정의되어 불필요한 렌더링이 자주 발생했습니다.
또한, 강결합 되어있다고 생각했던 비즈니스 로직은 사실 동영상-피드백 싱크 로직과 피드백 CRUD로직으로 분리 가능했으며, 두 로직이 한 컴포넌트에 혼재하고 있었습니다.
리렌더링 원인 추적 및 컴포넌트 응집도 향상을 위해 우선 구조 리팩터링을 수행했습니다.
피드백 페이지의 로직을 크게 두 개로 나누면 영상과 피드백을 동기화하는 로직, 피드백을 수정하는 로직이 있습니다. 이 두 로직은 아래와 같이 다른 컴포넌트에서 재사용 가능합니다.
동기화되는 feedbackList
수정가능한 feedbackList
동기화되는 feedbackList
+ 수정가능한 feedbackList
따라서, 이 두 로직을 custom hook으로 추출합니다.
동기화되는 feedbackList
→ useSyncFeedbackList
수정가능한 feedbackList
→ useEditableFeedbackList
기존 피드백 페이지는 feedbackList의 item인 FeedbackBox를 추상화 시키지 않은 채로 렌더링하고 있습니다.
<FeedbackArea>
<FeedbackArea.FAScrollView>
{feedbackList.map((feedback, idx) => (
<FeedbackBox
key={feedback.id}
onClick={(e) => handleClickFeedback(e, feedback.startTime)}
ref={(elem) => (feedbackRef.current[idx] = elem)}
>
<FeedbackBox.StartTime>{feedback.startTime}</FeedbackBox.StartTime>
<FeedbackBox.Content
value={feedback.content}
onChange={(e) => handleChangeFeedback(e, feedback.id)}
readOnly={feedback.readOnly}
/>
<FeedbackBox.Btn onClick={() => handleDeleteFeedback(feedback.id)}>
<DeleteIcon width={20} />
</FeedbackBox.Btn>
<FeedbackBox.Btn onClick={() => handleToggleEditFeedback(feedback.id)}>
{ feedback.readOnly ? <EditIcon width={20} /> : '수정완료'}
</FeedbackBox.Btn>
</FeedbackBox>
))}
</FeedbackArea.FAScrollView>
<FeedbackArea.FATextArea onInsertFeedback={addFeedback} />
</FeedbackArea>
이러한 구조에서는 React.memo()
를 적용하기 어렵습니다. 따라서 FeedbackBox
를 자식 컴포넌트로 분리합니다.