8주차에 적어보는 7주차에 배운 Optimistic UI!
Optimistic UI를 배우고 처음으로 든 생각은 '이런게 선의의 거짓말이 아닐까?' 였다!
Optimistic-UI를 사용하지 않는 상황과 Optimistic-UI를 사용하는 상황을 비교하여 Optimistic-UI가 필요한 이유를 알아보자!
Optimistic-UI란 서버로부터 응답을 받기 전에 변환 결과를 시뮬레이션하고 UI를 업데이트하는 데 사용할 수 있는 패턴이다.
서버로부터 응답이 수신되고 나면, Optimistic-UI 결과는 버려지고 실제 서버 응답의 결과로 대체된다.
이렇게 텍스트만으로 보면 굉장히 어려운 이야기 같으니 기존 서버 응답 결과를 보여주는 상황과 Optimistic-UI를 보여주는 상황을 게시물 좋아요 기능 예시를 통해 비교하여 살펴보자!
- 좋아요를 누르면 백엔드에 likeBoard API 요청을 보내고
- 백엔드는 DB에 해당 정보를 요청하게 된다.
- DB는 좋아요 수를 올려두고, 올린 좋아요 수를 응답해준다.
- 해당 응답을 백엔드는 다시 브라우저에 응답해준다.
해당 과정은 보통의 빠른 컴퓨터에선 아무런 불편함을 느끼지 못하지만, 느린 환경의 컴퓨터에선 좋아요를 누른 후 한참 후에 숫자가 올라갈 수 있다.
- 좋아요를 누르면 백엔드에 likeBoard API 요청을 보내기 전에 기존 좋아요수 + 1을 미리 화면에 그려 보여준다.
- 이후 백엔드에 likeBoard API 요청을 보내고, 백엔드는 DB에 해당 정보를 요청하여 DB 좋아요 수를 올려두고 올린 좋아요 수를 응답해준다.
- 해당 응답을 화면에 업데이트 해준다. (유저 입장에서는 같은 값을 화면에서 보고 있으므로 변화를 인지할 수 없다.)
백엔드에게 API 요청을 보내기 전에 예상되는 값을 미리 보여줌으로써 유저는 빠른 사용자경험을 할 수 있다.
만약 중간에 네트워크 등의 문제로 백엔드 요청이 실패할 경우 이전 값을 응답받아 이전 값을 화면에 업데이트 해준다.
Optimistic-UI는 실패 확률이 낮고 틀려도 괜찮은 중요하지 않은 데이터를 보여줄 때 사용한다.
어드민 페이지에서의 고객명단, 결제 후 잔여 금액 등 데이터가 굉장히 중요하고 안정성이 필요할 땐 Optimistic-UI를 사용해선 안된다.
import { gql, useMutation, useQuery } from "@apollo/client";
import { useRouter } from "next/router";
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 router = useRouter()
const { data } = useQuery(FETCH_BOARD, {
variables: { boardId: router.query.boardId },
});
const [likeBoard] = useMutation(LIKE_BOARD);
const onClickLike = () => {
likeBoard({
variables: { boardId: router.query.boardId },
// Optimistic UI 설정 (가짜로 보여주는 미리보기 데이터)
optimisticResponse: {
likeBoard: (data?.fetchBoard.likeCount || 0) + 1,
},
// 2. cache 직접 수정 (OptimisticResponse에서 받은 값 data로 바꿔준 후, backend에서 data를 받아오면 받아온 진짜 data로 수정)
update(cache, { data }) {
cache.writeQuery({
query: FETCH_BOARD,
variables: { boardId: router.query.boardId },
data: {
fetchBoard: {
// id, typename은 필수입력
_id: router.query.boardId,
__typename: "Board",
likeCount: data.likeBoard,
},
},
});
},
});
};
return (
<>
<div>현재 좋아요 카운트: {data?.fetchBoard.likeCount}</div>
<button onClick={onClickLike}>좋아요 올리기</button>
</>
);
}
<참조: https://www.apollographql.com/docs/react/v2/performance/optimistic-ui/>