Optimistic Update 적용기

SangHyeon Lee·2025년 11월 25일

시작하며

9월달까지 정신없이 Prography 활동을 마치고, 또 11월 중순까지 취업 활동이 한바탕 휩쓸어서 이제서야 정리하게 되었다.

요번년도 초부터 Prography라는 IT 커뮤니티, 흔히 말해 IT 동아리를 하게 되었는데 생각치 못 하게 얻은 점들이 많았다.

이를 자소서 작성과 포트폴리오 업데이트에 사용하면서, 미쳐 넣지 못한 내용들이 많음을 느꼈다.

오히려 마이너스일 수 있는 것들이라거나, 포트폴리오에 넣기엔 내용이 적다든가...

이러한 점들을 정리해서 올리고, 회고하는 시간을 가지려 한다.

적용기

사실 이 전에는 낙관적 업데이트가 있다는 것만 알았고, 적용해본 적은 없었다.

마침 서비스 개발 이후 고도화할 시간이 있었고, 또 필요했기에 적용해보게 되었다.

상황은 다음과 같았다.

  • 체크 박스 동작에 대해 비동기 fetching이 이뤄졌다.

  • 체크 박스 UI였기에, 동기적으로 동작하는 것처럼 보일 필요가 있었다.

  • 사용자가 빈번하게 체크/해제 하는 상황이 생길 것이기에 빠른 반응 속도가 필요했다.

이러한 상황에서 여기엔 낙관적 업데이트가 있지 않으면 너무 불편할 것이라 생각해 적용해보게 되었다.

useOptimistc

처음에 고려했던 것은 React 19에 정식 편입된 useOptimistic이었다.

당시 고도화를 고려하며 React 19의 각종 hooks를 공부하고 있었는데, 여타 라이브라리를 사용할 필요 없이 낙관적 업데이트를 적용할 수 있어 깔끔해 보였다.

Transition을 비롯해 React 19 기능들을 적용하고 싶은 욕심도 있었다.

그러나, 결론만 말하자면 이 방식은 포기하게 되었다.

useOptimistic을 사용한다면, swr을 통해 관리하고 있는 서버상태(클라이언트 캐시)를 보존할 수 있어 재사용성을 높일 수 있다는 장점이 있다.

하지만 useSWR을 통한 GET, 체크 동작과 연결된 fetching의 PATCH, 그리고 useOptimistic의 optimisticValue라는 징검다리들의 연동 과정에 문제가 있었다.

순서대로 설명하자면 아래와 같다.

  1. 체크 동작으로 인해 동기적으로 optimisticValue를 체크박스에서 상태로 사용하고, fetching이 실행된다.

    이는 startTransition내부에 낙관적 업데이트 동작을 먼저 수행하도록 하기에 발생하는 순서이다.

    참고로, useOptimistic의 상태를 변경하는 함수는 action안에서 실행되어야 하기에 startTransition을 사용하게 되었다.

    RCC들만 사용하고 있었기 때문에 복잡성을 늘리고 싶지 않았기 때문이었다.


  1. startTransition안에 PATCH 동작이 완료된다면 이 action은 pending 상태에서 벗어나게 되어, 내부에 관련된 useOptimistic에선 초기값을 사용하게 된다.

    여기서 useOptimistic에 주입된 초기값은 useSWR에서의 데이터를 사용할 것이므로, fetching 이후에 관련 mutate를 실행하게 될 것이다.


  1. GET요청이 다시 발생하고, 해당 결과를 받아 리렌더링하여 useOptimistic에 초기값을 전달한다.

    변경된 초기값을 체크박스에서 반영하여 안정된 데이터를 사용하게 된다.


중간에 useOptimistic이 사용되면서 과정이 복잡해진 것에 더해, 헛점이 있다.

바로, mutate이후 Transition은 종료되어 useOptimstic에선 초기값을 사용하게 되는데, 이 땐 아직 새로운 GET 요청이 완료되기 전이라는 것이다.

즉, 낙관적 업데이트 -> 다시 원본 값으로 회귀 -> PATCH 반영된 GET 결과로 업데이트

이 과정이 발생하게 된다.

오히려 UX를 해치는 Flickering이 발생하는 것이다..!

swr의 bound mutate

useOptimistic방식이 제외되면서, 결국 서버 상태에 대해 직접 낙관적 업데이트 하는 방식을 선택하게 되었다.

useSWRMutation를 사용하는 방식과 bound mutate를 사용하는 방식 중 bound mutate를 선택했다.


현재 API관련 핸들링, useSWR과의 연결 과정을 전역적으로 처리하고 있었고, 이 과정에서 AI Generated 코드를 사용하고 있었다.

(FE팀원 분이 만들어주셨는데, 편리하기도 하고 좋은 구조를 발견하는 등 장점도 있었지만, 함부로 건드리기 애매한 단점이 있었다. 관련해서 추후 이야기 하게 될 것 같다.)

이 때, key값을 API 그룹과 메서드명 그리고 props를 사용해 배정해주어 useSWR을 만들어주도록 처리하고 있었다.

useSWRMutation은 정확한 key값을 전달해줘야 했기 때문에 우리 방식에는 어려운 점이 많았다.


이에, bound mutate를 사용했는데, 간단하게 Optimistic Data를 인자로 넘겨주만 됐다.

체크 표시 UI 에러

이상이 없어야 할 동작에 이슈가 발생했다.

체크 표시가 씹히는 경우가 있던 것이다.

해당 체크박스 컴포넌트에 전달되는 값을 체크해보니, 정상적으로 데이터는 전달되고 있어서 더 이해가 가지 않았다.


문제의 원인은 CSS 적용에 있었다.

기존에는 다음과 같은 방식으로 되어있었다.


function CheckBox(checked, ...props){
	return <input
  className="bg-white checked:bg-blue"
  type="checkbox" checked={checked}/>
}

아래와 같이 수정했더니 정상 동작 했다.

function CheckBox(checked, ...props){
	return <input
  className={checked?"checked:bg-blue":"bg-white"}
  type="checkbox" checked={checked}/>
}

해결된 이유를 몰라 Gemini와 대화하며 얻은 결론은, 제어권 문제가 있었다는 것이다.

제어권

이는 FE라면 누구나 들어봤던 제어/비제어 컴포넌트 이야기를 확장한 것으로 보였다.

결국 제어/비제어 컴포넌트는 제어권을 React에서 가지거나 브라우저가 가지는 이야기인 것이다.


이 상황 역시 같다.

기존 방식은 checked:를 통해 브라우저가 컴포넌트의 상태를 파악하고 CSS를 입히도록 하고 있다.

변경 이후, checked 상태 값을 통해 React에서 두 CSS 중 선택하도록 되어있다.


이 상황을 자세히 보자면, 애초에 CheckBox 컴포넌트가 checked 상태를 외부로부터 주입받지만 onChange를 처리하는 방식에서 문제가 있었다.

해당 컴포넌트를 사용하는 구조는 아래와 같다.

<label htmlFor="id" onClick={handleClick}>
	<CheckBox id="id" checked={checked} readonly/>  
</label>

CheckBox를 제어 방식으로 사용하고 있음에도 브라우저에게 제어권을 넘기려고 했던 것이었다..!


참고로, 제어/비제어 방식 모두 사용할 수 있도록 만드려면 value 인자로 들어오는 값의 존재 여부에 따라 처리하면 된다고 한다.

radix 같은 곳에서 자주 사용하는 방식이라고 하니, 직접 만들 때 참고하면 좋을 듯 하다.

고도화

추가적인 고도화 사항으로, 체크 동작으로 인해 리스트에서 순서가 바뀌는 케이스가 많았기에 UX를 개선하고자 애니메이션을 추가했다.


motion의 AnimatePresense와 layout을 사용해 위치 변경에 대한 애니메이션을 추가하고,

비동기 동작으로 최종 업데이트 된 GET데이터를 반영하기 전에 opacity를 조절해 레이아웃 애니메이션 동작을 사용자에게 알리는 방식으로 처리했다.

마치며

처음으로 Optimistic Update를 적용해봤는데, 적확한 상황에 알맞은 방식으로 사용하지 않는다면 해가 될 수 있음을 느꼈던 경험이었다.


동작 메커니즘에 대해 몰랐다면 이슈 원인을 파악할 수 없었을 것이므로, 기술을 적용할 때 배경지식의 중요성도 느끼게 되었다.


추가로, 리액트 동시성 처리와 브라우저 동작에 대해 궁금증이 커졌다. 특히, 브라우저가 어떻게 동작하는지가 흥미로웠는데 문제는 이를 공부하기 어렵다는 것이다.

당장 Next든 React든 FE기술들에 대해서는 공식문서들이 많고, 관련 컨퍼런스 등을 통해서도 많은 것들을 배울 수 있지만, 웹 FE가 종속되어있는 브라우저에 대해서는 제대로 공부할 수 있는 문서가 없어보였다.

누군가 '근본'에 대한 중요성을 설파하던데, 이 근본이라는 것이 정말 어려운 것 같다.

정말 파고들자면 브라우저 동작까지 갈 것 같은데, 이에 대한 자료가 있을까?
AI는 피상적으로 인터넷 환경에 널부러진 각종 자료들을 긁어 모아 정리해서 보여줄 뿐이라는 생각이 들며 기반에 대해 불안해졌다.


궁금증과 불안을 낳기도 했지만, 좋은 경험이었던 것은 분명하다.

비록 너무 하찮아보여 포트폴리오에 넣지는 못했지만, 잊어버리기엔 아까워 제일 처음으로 기록으로 남겼다.

다른 하찮은(?) 경험들도 계속 작성하여 되돌아 볼 수 있도록 하겠다.

profile
회고할 가치가 있는 개발을 하자

0개의 댓글