DYO 공부하는 블로그
[MusicMate] 프로젝트에서 학습한 내용 정리 (3) 본문
6. 낙관적 업데이트
낙관적 업데이트는 서버 응답을 기다리지 않고, 사용자 입력에 따라 먼저 UI를 업데이트한 다음 나중에 서버 응답 결과에 따라 처리하는 방식이다.
- 사용자의 액션에 즉시 반응해 결과를 렌더링
- 서버 응답이 실패하면 롤백하거나 에러 처리
반대 개념으로는 서버 응답이 올 때까지 기다렸다가 UI를 업데이트하는 비관적 업데이트가 있다.
- 적용하면 좋은 경우
- 실패 확률이 낮고 복구가 쉬운 경우
- 사용자 체감 속도가 중요한 경우 ( 댓글 작성, 좋아요, 팔로우 )
- 모바일 네트워크 환경처럼 지연이 잦은 경우
- 프로젝트에 적용
이번 프로젝트에는 피드 submit 동작에 대해서 낙관적 업데이트를 적용했었다. infinity Sroll과 realtime을 통해 피드들을 로드했기 때문에 hasMore(해당 방향에 추가 피드가 있는지 판별) 작동에 따라 문제가 생기는 경우도 있었고, 오디오나 이미지 파일 업로드에 대해서 서버의 자원을 한번 더 소모하는 것보다 피드 보내는 단계에서 처리하고 피드를 업데이트 하는 것이 자원상 이득이라고 생각했기 때문이다.
- 하지만 전체 데이터를 낙관적으로 처리할 수 없음
게시물 ID를 서버단에서 UID를 생성해 사용하고 있었다. 낙관적 업데이트로만 적용했을 경우 클라이언트에서 좋아요, 대댓글 기능과 같은 기능이 피드 ID에 종속적이었기 때문에 모든 데이터를 낙관적으로 업데이트하는 것은 불가능했다.
따라서 supabase에서 insert시 select를 이용하면 성공 처리에 따라서 전체 목록을 불러오는 점을 이용해 ID를 가져와 처리했다. 클라이언트에서 이미 처리된 오디오 데이터나 이미지 데이터를 사용하긴 하나, 성공 여부 관계없이 렌더링하지는 않으므로, 낙관적 업데이트를 완전히 적용했다고 보긴 어렵다.
const { data, error: dbError } = await supabase
.from("feeds")
.insert({
title,
content,
audio_url,
image_url,
channel_id,
message_type,
})
.select("*");
7. useEffect는 만능 키가 아니다.
useEffect에 대한 이해가 옅였을 때 프로젝트를 시작하게 되었다. useEffect가 '부가 효과'를 의미하는 것처럼 느껴져서 items 업데이트 동작에 대해서 여러 로직과 동작을 넣었었는데, 처음엔 매우 잘 동작했다. 데이터가 들어왔을 때 자동으로 처리해야 할 부가 동작들을 처리하고. 추가 이벤트 분기에 대해서 알아서 처리해주고. 그렇게 하다보니 한 컴포넌트에 종속되는 useEffect들이 정말 많아졌다.

Effect가 늘어나다 보니 작성하는 중간에 내가 원치 않는 동작이 일어날 확률이 올라가고, 이는 디버깅의 여려움과 기능 추가의 어려움을 야기했다. 위의 표에 정리된 Effect들도 종속성을 쳐낼 걸 쳐내고, SRP를 유지하도록 해 명확성을 높여서 처리했다. ( 사실 뺄 수 있었지만 프로젝트 진행이 바빠 리팩터링할까 고민하다가 일단 부족한 기능을 채워야겠다고 생각했다. )
필요한 동작들도 있지만 충분히 뺄 수 있는 동작들도 있다. 멘토님이 추천해주신 React 공식 문서가 정말 좋더라.
Effect가 필요하지 않은 경우 : React.dev
Effect가 필요하지 않은 경우 – React
The library for web and native user interfaces
ko.react.dev
짧게 요약하면 모든 동작에 대한 부가 효과를 Effect에 종속시키지 말고 Effect가 필요 없을 때는 어떨 때는 필요한 동작을 여러 곳에서 중복 작성해서 호출하고, 이벤트로 처리할 수 있는 것은 이벤트 내부에서 처리하면 된다는 이야기다.
8. 렌더링 트리거(setState)연속 호출과 React의 렌더링 트리거 최적화
채널 페이지에 대해서 다른 사람과 동시에 작성하다 보니 useEffect와 로직 처리에서 setState 동작이 많게는 6번, 적게는 2번씩 작동하고 있었다. 너무 신경쓰여서 리팩터링하면서 삽질중이었는데, 제미나이한테 물어보다가 한소리 들었다.
'React는 똑똑합니다. (복습)'
리액트의 렌더링 트리거 중복 호출에 대한 동작에 대해서 알아보자.
*React 19 버전 기준*
- 자동 배칭 ( React 18 이후 )
이벤트 핸들러 뿐 아니라 useEffect, setTimeout, Promise then 등 등일한 턴에서 발생한 여러 state를 한번의 렌더로 묶고, 이벤트가 끝난 뒤 한 번의 렌더만 호출한다.
- 마지막 값만 커밋됨
동기적으로 여러 번 setState 동작이 일어날 경우 큐에 쌓였다가 순서대로 계산되지만 결과적으로 마지막 값으로 렌더가 커밋된다.
- diff 최적화
렌더가 발생하더라도 이전 가상 트리와 비교해서 필요한 부분만 DOM을 변경하고, 바뀐 것이 없다면 DOM 관련 작업이 없으므로 렌더링 자원을 소모하지 않습니다.
결과적으로 작성할 때 렌더링 트리거의 동작을 반드시 고려해야겠지만, 최적화에 의해서 생각보다 자원이 적게 드는 작업이므로 모든 동작에 대해서 '이를 악물고' 렌더링 트리거 동작을 관리할 필요까지는 없다는 것이다.
이번 프로젝트 회고 요약
- 낙관적 업데이트는 체감 속도를 높여주지만, ID 종속 기능에서는 한계가 있다.
- useEffect를 남용하면 유지보수와 디버깅이 어려워지므로, 불필요한 의존을 줄이고 이벤트/계산으로 분리하는 게 중요하다.
- React의 자동 배칭·마지막 값 커밋·diff 최적화 덕분에 연속적인 setState 호출은 생각보다 자원 소모가 적다.
'Project > MusicMate' 카테고리의 다른 글
| [MusicMate] 프로젝트에서 학습한 내용 정리 (2) (7) | 2025.08.09 |
|---|---|
| [MusicMate] 프로젝트에서 학습한 내용 정리 (1) (2) | 2025.08.07 |
| [MusicMate] 팀 프로젝트 - 프로젝트 시작 (3) | 2025.07.24 |