
낙관적 업데이트
낙관적 업데이트란 서버로부터 응답을 받기 전 UI를 미리 업데이트 하는 방법이다.
사용자에게 빠른 피드백을 줄 수 있기 때문에 자주 사용된다.
그러나 서버로부터의 응답이 실패했을 경우 롤백하는 적절한 처리가 필요하며, 무분별하게 사용하면 사용자로부터 신뢰를 잃을 수 있으므로 빠른 상호작용이 중요하지만 중요하지 않은 데이터에 적용할 수 있겠다.
나의 경우, <코코> 프로젝트에서 낙관적 업데이트를 적용했고, 서버로부터 에러가 발생했을 때 롤백하는 로직까지 구현해보았다.
좋아요 기능 같은 경우는 즉각적으로 반영되는 것이 사용자의 입장에서 필수적인 경험이라고 생각했기 때문이다.
낙관적 업데이트 시나리오
- 흐름: 좋아요 관련 local state 변경 → UI 렌더링 → 좋아요 api 호출
- 롤백: local state 변경 → 서버 에러 → local state 원상복귀
구현
(1) 로컬 state
const [localLike, setLocalLike] = useState(liked);
const [localLikeCount, setLocalLikeCount] = useState(likeCount);
로컬 state를 선언해준다. 이때 초기값으로 설정 되어 있는 liked 와 likeCount 는 서버로부터 얻은 데이터이다. (props로 내려준 형태)
초기 서버 값과 로컬 상태가 동기화되어 있는 상태다.
(2) 좋아요 기능 함수 - 낙관적 업데이트 로직
const handleLike = async () => {
// 좋아요 취소
if (localLike && localLikeCount > 0) {
// 낙관적 업데이트
setLocalLike(prev => !prev);
setLocalLikeCount(prev => prev - 1);
try {
await deleteLikeMutation.mutateAsync(postId);
} catch {
// 롤백
setLocalLike(prev => !prev);
setLocalLikeCount(prev => prev + 1);
}
}
// 좋아요 등록
else if (!localLike) {
// 낙관적 업데이트
setLocalLike(prev => !prev);
setLocalLikeCount(prev => prev + 1);
try {
await registerLikeMutation.mutateAsync(postId);
} catch {
// 롤백
setLocalLike(prev => !prev);
setLocalLikeCount(prev => prev - 1);
}
}
};
localLike 와 localLikeCount 상태를 먼저 변경한 후 api를 호출하도록 구현하였다.
요청이 실패할 경우 해당 state를 다시 원상복귀 하는 롤백 로직은 catch문을 통해서 수행된다.
(Promise가 reject될 경우 catch문 수행되므로)
(3) UI
{/* 좋아요 버튼 */}
<button
onClick={handleLike}
disabled={isAnimating}
>
{/* 하트 아이콘 */}
<div className="relative">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
className={`
transition-all duration-300 ease-out
${localLike ? 'fill-current scale-110' : 'fill-none'}
${isAnimating ? 'animate-pulse' : ''}
`}
stroke="currentColor"
strokeWidth="2"
>
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
</svg>
</div>
{/* 좋아요 카운트 */}
<span
className={`
font-medium transition-all duration-300 ease-out
${isAnimating ? 'scale-105' : ''}
${localLike ? 'text-red-600' : ''}
`}
>
{localLikeCount}
</span>
</button>
좋아요 버튼 내에서 localLike , localLikeCount에 따라 좋아요 여부를 표시하여 빠른 반영이 가능하다.
'웹 > React' 카테고리의 다른 글
| [React 딥다이브🌊] fiber 아키텍처와 동기 렌더링-커밋 프로세스 코드 뜯어보기 (4) | 2025.08.03 |
|---|---|
| [React] memo, useCallback을 사용한 렌더링 최적화 (3) | 2025.07.08 |
| [코드 개선하기] try, catch vs React Query의 onSuccess, onError : 비동기 처리 로직 통일하기 (0) | 2025.06.25 |
| [React] 폰트 최적화 + preload 적용해 사용자 경험 개선하기 (0) | 2025.06.23 |
| [React, Vite, Vite-bundle-analyzer] CSR에서 초기 로딩 시간 단축하기 2 - 라이브러리 청크 단위로 정적 분리 (0) | 2025.06.03 |