낙관적 UI 업데이트의 원리

392025년 09월 23일3

useOptimistic 훅이 보여준 마법 같은 동작에 팀원들은 열광했지만, 세바스찬 마크바게는 한 걸음 더 나아가 그 마법 뒤에 숨겨진 원리를 명확히 해야 한다고 생각했다. 개발자들이 이 훅을 올바르게 사용하려면, 그 내부 동작의 멘탈 모델을 이해하는 것이 중요했기 때문이다.

useOptimistic은 마법이 아닙니다. 이것은 매우 단순하지만 강력한 상태 관리 원칙에 기반하고 있습니다.”

그는 화이트보드에 두 종류의 상태를 나란히 적었다.

  • Source of Truth (진실의 원천): 실제 서버의 데이터와 동기화되는, 신뢰할 수 있는 상태. (예: useState로 관리되는 likes)
  • Optimistic State (낙관적 상태): 사용자에게 즉시 보여주기 위한, 임시적인 가상 상태. (예: useOptimistic이 반환하는 optimisticLikes)

“핵심은 이 두 상태를 분리하는 것입니다.”

세바스찬은 useOptimistic 훅의 동작을 단계별로 풀어 설명했다.

useOptimistic 훅은 내부적으로 항상 ‘진실의 원천’이 되는 실제 상태를 알고 있습니다. 처음 훅이 호출될 때, const [optimisticLikes, addOptimisticLike] = useOptimistic(likes, ...) 처럼 우리는 실제 상태 likes를 인자로 넘겨주죠. 이때 낙관적 상태는 실제 상태와 동일한 값을 가집니다.”

그는 그림을 그렸다. [실제 상태: 10][낙관적 상태: 10]

“사용자가 버튼을 클릭해서 addOptimisticLike(1)을 호출하면, 이 함수는 오직 ‘낙관적 상태’만 변경합니다. 실제 상태는 전혀 건드리지 않죠. 그리고 즉시 리렌더링을 유발합니다.”

[실제 상태: 10]는 그대로, [낙관적 상태: 11]로 변경된다. 화면에는 optimisticLikes의 값인 11이 보인다.

“이후 백그라운드에서 비동기 작업(서버 요청)이 진행됩니다. 이 작업이 끝나기 전까지는, React는 계속해서 이 두 개의 분리된 상태를 유지합니다.”

이제 중요한 분기점이 온다. 비동기 작업이 끝났을 때.

시나리오 1: 작업 성공

“작업이 성공하면, 우리는 보통 setLikes(newLikesFromServer)처럼 실제 상태를 서버가 보내준 새로운 값으로 업데이트합니다. useOptimistic 훅은 이 ‘진실의 원천’이 되는 실제 상태가 변경된 것을 감지합니다. 그리고는 자신이 관리하던 낙관적 상태를 버리고, 새로운 실제 상태 값을 자신의 값으로 즉시 채택합니다.”

[실제 상태: 11]로 변경 → [낙관적 상태: 11]로 동기화. 화면은 11을 그대로 유지하며, 상태가 일치하게 된다.

시나리오 2: 작업 실패

“작업이 실패하면, 우리는 실제 상태를 업데이트하는 코드를 호출하지 않겠죠. 비동기 함수가 끝나고 콜 스택이 비워지면, useOptimistic 훅은 ‘진실의 원천’이 되는 실제 상태가 전혀 변경되지 않았다는 사실을 확인합니다. 그러면 React는 다음 렌더링 사이클에서, 자신이 임시로 만들었던 낙관적 상태를 버리고, 원래의 실제 상태 값으로 되돌아가는 리렌더링을 자동으로 예약합니다.”

[실제 상태: 10]가 그대로임 → [낙관적 상태: 10]으로 자동 롤백. 화면의 숫자가 스르륵 10으로 되돌아온다.

이것이 바로 useOptimistic의 원리였다.

그것은 React가 두 개의 상태, 즉 ‘진실’과 ‘희망’을 동시에 관리하다가, 비동기 작업의 결과에 따라 ‘희망’을 ‘진실’로 확정하거나, 혹은 ‘희망’을 버리고 ‘진실’로 되돌아가는 지능적인 메커니즘이었다.

이 단순하지만 강력한 원리를 이해하자, 팀원들은 useOptimistic이 단지 ‘좋아요’ 버튼만을 위한 훅이 아님을 깨달았다. 댓글 달기, 메시지 전송, 목록에 아이템 추가/삭제 등, 서버와의 통신이 필요한 거의 모든 사용자 상호작용에 적용하여, 사용자 경험을 한 차원 끌어올릴 수 있는 범용적인 해결책이었다.