낙관적 업데이트에 관하여 설명해주세요

ChoiYongHyeun·2024년 12월 12일
2

브라우저

목록 보기
12/16

낙관적 업데이트란 ?

낙관적 업데이트란 클라이언트와 서버 단의 인터렉션에서

클라이언트의 상태 변경을 인터렉션 성공 유무에 상관 없이 성공 할 것이라 낙관적으로 생각하고 업데이트 하는 것을 의미한다.

즉, 클라이언트의 상태 변경을 서버의 응답값이 아닌 클라이언트 단에서 일어난 액션을 중심으로 하는 행위를 의미한다.

낙관적 업데이트가 왜 필요할까 ?

이런 낙관적 업데이트를 사용하는 이유는 단 한가지이다.

바로 클라이언트 단에서의 더 빠른 상태 변경을 통해 유저 경험을 향상시키고자 하는 것이다.

서버와 클라이언트 간의 통신에서는 다양한 변수가 존재한다.

이에 클라이언트의 상태 변경이 통신의 응답값에 영향을 받게 된다면 네트워크 요청이 길어 질 수록 화면의 상태가 변경되는데까지 걸리는 시간은 오래 걸릴 수 밖에 없다.

네트워크 요청의 성공 유무가 크게 중요하지 않고 성공 할 확률이 매우 높은 요청임에도 불구하고 모든 요청의 결과를 기다리는 것은 자칫하면 매우 답답하게 느껴질 수 있다.

생각해보자

결제를 할 때 결제가 완료 될 때 까지 로딩 스피너가 돌아가는 것은 참을 수 있다.

하지만 단순히 좋아요를 누를 때 마다 로딩 스피너가 돌아가는 것은 답답해서 참을 수가 없을 것이다. (적어도 나는 그렇다.)

낙관적 업데이트의 예시

가장 흔하게 낙관적 업데이트가 사용되는 영역은 아마 좋아요나 추천 등과 같이 인터렉션의 성공 유무가 크게 중요하지 않으면서

요청 후 클라이언트 단에서 상태가 변경 되어야 하는 부분들일 것이다.

낙관적 업데이트를 구현하는 방식은 매우 다양하다.

리액트 쿼리에서는 두 가지 방법을 제시[1] 하는데 그 중 내가 사용했던 방법을 예시로 들고자 한다.

const MarkingItemLikeToggle = () => {
  const { markingId, countData, isLiked } = useMarkingItemProps(); // 실제 서버로부터 받은 데이터
  const [_isLiked, _setIsLiked] = useState<boolean>(() => isLiked); // 낙관적 업데이트에 이용 할 가상의 상태 
  const [_likedCount, _setLikedCount] = useState<number>(
    () => countData.likedCount,
  );

  const { mutate: postLikeMarking, isPending: isPostLikeMarkingPending } =
    usePostLikeMarking();
  
... 


  const handleClickLikeButton = () => {
    // 낙관적 업데이트 적용 
    _setIsLiked(true);
    _setLikedCount((prev) => prev + 1);

    postLikeMarking(
      { markingId },
      {
        onError: () => {
          // 요청 실패 시 낙관적 업데이트 취소
          _setIsLiked(false);
          _setLikedCount((prev) => prev - 1);
        },
      },
    );
  };
  
  return (
  ...
        {_isLiked ? (
        <button
          aria-label={`${markingId} 번 마킹 좋아요 취소`}
          onClick={handleClickUnLikeButton}
          disabled={isPending}
          className="text-tangerine-500"
        >
          <FilledLikeIcon />
        </button>
      ) : (
        <button
          aria-label={`${markingId} 번 마킹 좋아요 추가`}
          onClick={handleClickLikeButton}
          disabled={isPending}
        >
          <LikeIcon />
        </button>
      )}
...
  )
  

위 코드의 예시를 보면 onError 를 통해 성공 할 것이라 예상했던 요청이 실패한 경우 낙관적 업데이트를 취소하는 모습을 볼 수 있다.

이는 낙관적 업데이트를 사용하며 주의해야 하는 점으로 , 낙관적으로 업데이트하는 것은 좋지만 가장 우선해야 하는 것은 클라아인터단의 상태와 서버에서의 상태가 일치해야 한다는 점이다.

해당 예시에서는 매우 단순하게 좋아요 버튼의 색칠을 제거하는데에서 그쳤지만

서버 요청이 실패했음을 더욱 확실히 알리고 싶은 경우엔 경우에 따라 다양한 솔루션을 제공 할 수 있다.

그렇다면 낙관적 업데이트는 항상 옳을까 ?

빠른 반응을 통해 유저의 경험을 늘린다는 점에 잇어서 낙관적 업데이트는

모든 컴포넌트가 갖춰야 할 덕목처럼 느껴질 수 있다.

하지만 가끔은 업데이트를 서버 상태에 맞추거나 더 지연시키는 것이 도움이 될 때도 있다.

  1. 실패 할 확률이 높은 요청의 경우

회원가입이나 로그인과 같이 실패 할 확률이 높은 요청에서 낙관적 업데이트를 시행하게 되면 다음과 같은 흐름으로 유저가 경험 할 것이다.

잘못된 양식으로 회원가입 -> 회원가입 완료 (낙관적) -> 회원가입 실패 (실제 서버 상태) -> 회원 가입 페이지로 리다이렉션 및 알림

이것은 괜스레 낙관적 업데이트를 하려다 부정적인 기억만 심어줄 수 있다.

유저는 긍정적인 부분들보다 부정적인 부분이 더 크게 와닿기에 [2] 최대한 부정적 기억을 주지 않도록 주의해야 한다.

실패 할 확률이 높은 경우는 유저 -> 서버로 보낼 때 뿐 아니라 서버 내에서 복잡한 로직이 필요한 경우에도 그렇다.

  1. 유저에게 신뢰감을 줘야하는 요청의 경우

중요한 데이터를 다룰 때에는 오히려 업데이트를 서버 상태에 맞춰 하거나 오히려 지연 시키는 것이 유저에게 신뢰감을 심어 줄 수 있다.

계좌 정보들을 업데이트하거나 변경하는 것과 같이 중요한 데이터는 순식간에 처리 되는 것 보다 '처리중입니다 ..' 라는 문구가 나타나는 것이 서비스가 더 조심히 처리하는 듯한 느낌을 줄 수 있다.

의도적으로 로딩 페이지를 보여주기 위해 지연시키는 경우도 있다.

츨처 : 사용자 경험을 위한 의도적인 비효율 [3]

출처

1. Optimistic Updates | TanStack Query React Docs
2. 알아두면 좋은 21 Laws of UX (2)
3. 사용자 경험을 위한 의도적인 비효율 | Univdev

profile
빨리 가는 유일한 방법은 제대로 가는 것이다

2개의 댓글

comment-user-thumbnail
2024년 12월 18일

낙관적 업데이트를 경험하고 문서까지 작성하면서 항상 옳은가?에 대해서는 생각해보지 않았었는데 덕분에 낙관적 업데이트에 대해 더 자세히 알게 되었네요. 감사합니다 ㅎㅎ

1개의 답글