ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Portfolly] 좋아요/북마크 버튼 기능 만들기
    Studying/Proj 과정 2023. 7. 10. 14:28

    개요

    Portfolly 프로젝트에서 '좋아요/북마크' 기능을 담당하게 되었습니다. 구현하면서 겪은 여러가지 문제들, 해결 과정, 코드 리펙토링을 일어난 순서대로 기록합니다.

     

     

    프로젝트 환경

    • React
    • TypeScript

     

    useToggle 커스텀 훅 만들기

    좋아요, 북마크 버튼은 공통점이 많은 요소입니다. 버튼의 모양만 다를 뿐 마우스 클릭으로 on, off를 설정하는 것이 기능면에서 거의 똑같습니다.

     

    처음에는 useLikeBtn, useBookmarkBtn 이라는 두 개의 Custom hook을 만들었습니다. 그런데 각각 좋아요, 북마크에만 사용할 수 있다는 점에서 훅으로 분리할 큰 이유를 느끼지 못 했습니다.

     

    동작하는 코드가 그리 길지 않아서 차라리 LikeButton 컴포넌트와 BookmarkButton 컴포넌트에 onClick 이벤트 함수로 작성하는 게 나았습니다.

     

    하지만 두 버튼이 매우 비슷하다는 점을 이용하고 싶었고, on/off 기능을 가지는 useToggle 커스텀 훅을 만들었습니다.

    아래에서 코드를 살펴보겠습니다.

     

    일단 두 개의 import문이 존재합니다.

    import { useCallback, useState } from 'react';
    import { call } from '@/utils/apiService';

    call 의 경우 API요청을 위한 axios 호출을 call 이라는 함수로 분리해 가져와 쓰는 겁니다. 아래 링크에 자세히 블로깅 했습니다.

     

     

    [Ajax] axios를 기반으로 한 call 함수 vs 커스텀 훅

    [Ajax] axios를 기반으로 한 call 함수 vs 커스텀 훅 Studying/React.js 2023. 7. 6. 10:00

    all-done.tistory.com

     

    useToggle 훅은 여러 개의 인자를 받습니다. 이를 Toggle 인터페이스로 정의했습니다.

    export interface Toggle {
      buttonType: 'likes' | 'bookmarks';
      portfolioId: number;
      isToggled: boolean;
      color: string;
      count?: number;
    }
    • buttonType - 버튼의 종류입니다. 현재는 like 버튼과 bookmark 버튼이 있습니다. 네이밍이 이상하다고 느껴질 수도 있는데, 서버에 API요청을 할 때 URL 경로가 likes, bookmarks 로 되어있어서 저렇게 적었습니다.
    • portfolioId - 좋아요, 북마크를 할 포트폴리오 Id 입니다. 어떤 포트폴리오에 좋아요, 북마크를 했는지 알고 서버에 요청을 보낼 때 필요합니다.
    • isToggled - 현재 on/off 상태를 나타냅니다.
    • color - 버튼의 색상입니다. 클릭되었을 때 원하는 색상을 초기 인자로 전달 받습니다.
    • count? - 클릭된 횟수입니다. 좋아요 버튼에만 존재하기 때문에 ? 기호를 붙였습니다.

     

    이제 useToggle 훅입니다. 위에서 말한 Toggle 타입의 객체를 인자로 받습니다.

    export default function useToggle({ portfolioId, buttonType, isToggled: isOn, color, count: cnt = 0 }: Toggle) {
      const [count, setCount] = useState<number>(cnt);
      const [isToggled, setIsToggled] = useState<boolean>(isOn);
      const [url] = useState<string>(`/${buttonType}/${portfolioId}`);
      const [buttonColor, setButtonColor] = useState<string>(isOn ? `${color}` : 'gray');
    
      const changeToggleClicks = () => call(url, 'GET');
    
      const onClick = async () => {
        if (isToggled)
          await changeToggleClicks();
          setButtonColor('gray');
          buttonType === 'likes' && setCount(count - 1);
    
        if (!isToggled)
          await changeToggleClicks()
          setButtonColor(color);
          buttonType === 'likes' && setCount(count + 1);
    
        setIsToggled(!isToggled);
      }
    
      return [buttonColor, onClick, count] as const;
    }

    초기값으로 받은 인자들을 모두 state에 저장합니다.

     

    그리고 call 함수는 call 이라고만 적으면 명시성이 떨어져 changeToggleClicks 라는 이름의 함수로 감싸줬습니다. 아직 네이밍에 있어서는 센스가 많이 부족한 것 같습니다.

     

    이제 onClick 이벤트 핸들러입니다.

     

    토글이 on 상태였다면(isToggled) off로 변해야 하기 때문에 color를 gray로 바꾸고, 좋아요 버튼인 경우 count도 -1 합니다.

    토글이 off 상태였다면(!isToggled) on으로 변해야 하기 때문에 color를 인자로 넘겼던 색상으로 바꾸고, 좋아요 버튼인 경우 count도 +1 합니다.

     

     

    useCallback을 사용하면 좋을까?

    위 코드에서는 적지 않았지만, 처음에는 useCallback으로 onClick 함수를 메모이제이션 했습니다.

     

    그렇게 만들면 onClick이 첫 렌더링 때 생성된 후, 의존값이 바뀌지 않는 이상 기존 함수를 계속 반환합니다.

     

    그런데 생각해보니 PortfolioDetail 페이지가 자주 리렌더링 되는 것도 아니고, 굳이 가벼운 onClick 함수를 메모이제이션 할 필요가 없다고 생각해 useCallback을 제거하였습니다. 게다가 의존값을 isLike로 정해야 Toggle이 이루어지는데, 그럼 플립플롭 하면서 어차피 다시 재생성되는 거였습니다.

     

    아래는 useCallback에 대해 공부할 때 참고한 블로그입니다.

     

    React Hooks: useCallback 사용법

    Engineering Blog by Dale Seo

    www.daleseo.com

     

     

    아직 부족한 점

    북마크, 좋아요 요청이 실패했을 때 '요청에 실패했습니다'라는 alert를 띄우지만 버튼 색과 카운트 UI는 변경되어있습니다. 단 새로고침을 하면 다시 이전 상태가 됩니다. useToggle에서 changeToggleClick()을 통해 API 요청을 보내고 정상일 때만 setButtonColor 등의 처리를 해야하는데 그냥 해버리기 때문입니다.

     

    try-catch 문을 사용하면 될 것 같은데 코드 안에 넣기 싫을 정도로 지저분해서 다른 방법을 사용하고 싶습니다.

     

     

    다음에 시도해보고 싶은 방법들

    react-query를 사용한 '낙관적 업데이트(optimistic update)' 방법이 있다고 합니다.

     

    좋아요 버튼을 예시로 설명하자면,

     

    일반적인 좋아요 기능은

    좋아요 요청 → 서버에서 처리 → 정상 응답 → UI 업데이트(하트 색 채우기 등)

    라는 과정을 거치게 됩니다.

     

    여기서 낙관적 업데이트라는 것을 사용하면,

    좋아요 요청 → UI 업데이트 → 서버에서 처리 → 성공이면 유지, 실패면 복원

    라는 과정을 거치게 됩니다.

     

    서버 요청이 정상 작동 할 것이라는 낙관적인 가정 하에 UI를 먼저 업데이트 시키고, 서버를 통해 검증 받은 뒤 실제 처리에 따라 업데이트 하는 방식입니다.

     

    이렇게 하면 서버에 API요청을 보내고 응답을 받기까지 생기는 딜레이를 해소할 수 있습니다.

     

    이 방법으로 구현했을 때와, 위에서 작성한 코드로 구현했을 때 어떤 게 더 스타일에 맞는지 비교해보고 싶습니다.

     

Designed by Tistory.