Optimistic UI

이주희·2022년 5월 1일
0

React ♥️ Next.js

목록 보기
39/48

빠르게 할 수 없다면, 속여보자! ㅋㅋ >> Optimistic UI

  • 말 그대로 낙관적인 UI이다.
  • 전제 조건
    실패할 가능성이 낮고, 실패해도 문제가 없는 데이터에 적용한다.
    (가이드: 99% 성공 확률이 있을 때만 사용한다.)
  • 배제해야 하는 경우
    여러 테이블에 데이터를 함께 저장하는 경우, 하나라도 실패하면 전체 API가 실패로 처리되기 때문에 실패 확률이 높다.

좋아요 프로세스 👍🏻

브라우저에서 좋아요를 누르면 백엔드에 likeBoard API를 요청하고 DB에 접근해서 기존의 좋아요 수에 +1을 한다.
DB에서 돌려받은 값을 백엔드를 거쳐 브라우저에서 받아서 화면에 보여준다.

👇🏻 Optimistic UI를 적용하면 ~

  1. globalState에 좋아요의 값을 +1 해놓고 globalState를 화면에 먼저 보여준다.

  2. DB에서 받은 값과 globalState의 값이 일치하는지 확인한다.

  3. 결제처럼 중요한 데이터가 아니고, 실패할 가능성이 낮은 경우에 요런 눈속임으로 더 빠른 경험을 제공할 수 있다.
    (useMutation을 사용하면 apollo의 globalState에 자동으로 저장이 되므로, globalState에 따로 저장해 줄 필요는 없다.)


optimistic UI를 이용하면 1,2번 방법을 이용했을 때보다 속도가 훨씬 빠르다!!

mutation 결과를 화면에 보여주는 방법 세가지

  1. 결과를 result에 받고, state에 담아서 화면에 보여주는 방법
  2. refetchQueries로 다시 받는 방법
    (mutation 한 번, query 한 번, 요청을 총 두 번 하게 된다.)
      /* refetch - api가 LIKE_BOARD 한 번, FETCH_BOARD 한 번 해서 총 두 번 요청된다. */
      refetchQueries: [
        {
          query: FETCH_BOARD,
          variables: { boardId: "6269ecf7a8255b002988d65e" },
        },
      ],
  1. cacheState를 직접 수정하는 방법
    optimisticResponse를 같이 쓸 수 있다.

Optimistic UI 활용하기

어따 써..???
mutation 요청하는 함수의 variables 다음에 ,로 이어서 객체 형태로 적으면 된다.

1. optimisticResponse

mutation 함수 안에 optimisticResponse 써주면 API 응답이 오기 전에 보여줄 값을 지정할 수 있고, 응답이 오면 진짜 값으로 바꿔준다!
(좋아요일 경우, 성공하면 기존의 좋아요 수 + 1, 실패하면 기존의 좋아요 수가 그대로 들어온다.)

      optimisticResponse: {
        likeBoard: (data.fetchBoard.likeCount || 0) + 1,
          // 기존의 좋아요 수 + 1
          // undefined일 때는 0 + 1
      },

2. update

cacheState의 값을 직접 바꿔줄 수 있다.

      update(cache, { data }) {
        // data는 likeBoard API의 response로 받는 값이다.
        cache.writeQuery({
          // 기존의 데이터를 직접 바꾼다.
          query: FETCH_BOARD, // 바꿀 쿼리
          variables: { boardId: "6269ecf7a8255b002988d65e" }, 
          // 위에서 useQuery 선언할 때 쓴 쿼리명과 variables를 그대로 똑같이 써야 한다.
          data: {
            // 조작할 것을 적는다. 여기서는 data를 조작한다.
            fetchBoard: {
              _id: "6269ecf7a8255b002988d65e",
              __typename: "Board", 
              // id와 typename은 조건이다.
              // 이 두개의 조건을 가지고 globalState에 저장된 것에서 찾아내기 때문에 id와 typename은 둘 다 꼭~ 입력해야 한다.
              // likeCount:10, // 요로케 입력하면 기존에 fetch해온 값을 무시하고 여기에서 입력한 10이 된다. 
              // (백엔드의 값과는 상관 없이 조작만 하는 것이다.)
              likeCount: data.likeBoard, // data.likeBoard : likeBoard의 결과이다.
            },
          },
        });
      },

전체 코드

import { gql, useMutation, useQuery } from "@apollo/client";

const FETCH_BOARD = gql`
  query fetchBoard($boardId: ID!) {
    fetchBoard(boardId: $boardId) {
      _id
      likeCount
    }
  }
`;

const LIKE_BOARD = gql`
  mutation likeBoard($boardId: ID!) {
    likeBoard(boardId: $boardId)
  }
`;
export default function OptimisticUIPage() {
  const { data } = useQuery(FETCH_BOARD, {
    variables: { boardId: "6269ecf7a8255b002988d65e" },
  });

  const [likeBoard] = useMutation(LIKE_BOARD);

  const onClickOptimisticUI = () => {
    likeBoard({
      variables: { boardId: "6269ecf7a8255b002988d65e" },

      optimisticResponse: {
        // API 응답이 오기 전에 일단 이 값을 보여주고,
        // 응답이 오면 진짜 값으로 바꿔준다!
        // 좋아요가 실패하면 이전 값으로 들어오게 된다.
        likeBoard: (data.fetchBoard.likeCount || 0) + 1,
      },

      /* 직접 cache를 수정해서 globalState를 바꿔주는 방법, optimisticResponse를 같이 사용할 수 있다. */
      update(cache, { data }) {
        // data: likeBoard의 결과값
        // data는 likeBoard API의 response로 받는 값이다.
        cache.writeQuery({
          // 기존의 데이터를 직접 바꾼다.
          query: FETCH_BOARD,
          variables: { boardId: "6269ecf7a8255b002988d65e" }, // 위에서 useQuery 선언할 때 쓴 쿼리명과 variables를 그대로 똑같이 써야 한다.
          data: {
            // 조작할 것을 적는다. 여기서는 data를 조작한다.
            fetchBoard: {
              _id: "6269ecf7a8255b002988d65e",
              __typename: "Board", // 조건: 이 두개를 가지고 globalState에 저장된 것에서 찾아내기 때문에 id와 typename은 둘 다 꼭~ 입력해야 한다.
              // likeCount:10, // 요로케 입력하면 기존에 fetch해온 값을 무시하고 여기에서 입력한 10이 된다. (백엔드의 값과는 상관 없이 조작만 하는 것이다.)
              likeCount: data.likeBoard, // data.likeBoard : likeBoard의 결과이다.
            },
          },
        });
      },
    });
  };

  return (
    <>
      <div>좋아요 {data?.fetchBoard?.likeCount}</div>
      <button onClick={onClickOptimisticUI}>좋아요 올리기</button>
    </>
  );
}
profile
🍓e-juhee.tistory.com 👈🏻 이사중

0개의 댓글