useCallback, useMemo 사용해보기 + React.memo

J·2023년 7월 29일
0

todays-recipe

목록 보기
3/9

handleLikeButtonClick는 좋아요 버튼을 누르면 동작하는 함수이다.

기존 코드

  const handleLikeButtonClick = async () => {
    // 로그인 체크
    if (!currentUserUid) {
      alert('로그인이 필요합니다.');
      return;
    }
    // !좋아요인 경우
    if (!like) {
      // 데이터 추가
      await setDoc(doc(dbService, 'likes', currentUserUid), {
        userId: currentUserUid, // user id
        docId: recipe.RCP_SEQ, // filed id
        RCP_NM: recipe.RCP_NM, // 레시피명
        RCP_PAT2: recipe.RCP_PAT2, // 레시피 종류
      });
      // true로 상태 변경
      setLike(true);
      console.log('레시피 찜');
    } else {
      const isLiked = doc(dbService, 'likes', currentUserUid);
      // 이미 좋아요가 되어 있는 상태이면 삭제
      deleteDoc(isLiked);
      // false로 상태 변경
      setLike(false);
      console.log('레시피 찜 취소');
    }
  };

useCallback

    const handleLikeButtonClick = useCallback(async () => {
    // 로그인 체크
    if (!currentUserUid) {
      alert('로그인이 필요합니다.');
      return;
    }
    // !좋아요인 경우
    if (!like) {
      // 데이터 추가
      await setDoc(doc(dbService, 'likes', currentUserUid), {
        userId: currentUserUid, // user id
        docId: recipe.RCP_SEQ, // filed id
        RCP_NM: recipe.RCP_NM, // 레시피명
        RCP_PAT2: recipe.RCP_PAT2, // 레시피 종류
      });
      // true로 상태 변경
      setLike(true);
      console.log('레시피 찜');
    } else {
      // 이미 좋아요가 되어 있는 상태이면 삭제
      const isLiked = doc(dbService, 'likes', currentUserUid);
      deleteDoc(isLiked);
      // false로 상태 변경
      setLike(false);
      console.log('레시피 찜 취소');
    }
  }, [like, currentUserUid, recipe]);

useMemo

const handleLikeButtonClick = useMemo(() => {
    return async () => {
      // 로그인 체크
      if (!currentUserUid) {
        alert('로그인이 필요합니다.');
        return;
      }
      // !좋아요인 경우
      if (!like) {
        await setDoc(doc(dbService, 'likes', currentUserUid), {
          userId: currentUserUid, // user id
          docId: recipe.RCP_SEQ, // filed id
          RCP_NM: recipe.RCP_NM, // 레시피명
          RCP_PAT2: recipe.RCP_PAT2, // 레시피 종류
        });
        // true로 상태 변경
        setLike(true);
        console.log('레시피 찜');
      } else {
        // 이미 좋아요가 되어 있는 상태이면 삭제
        const isLiked = doc(dbService, 'likes', currentUserUid);
        deleteDoc(isLiked);
        // false로 상태 변경
        setLike(false);
        console.log('레시피 찜 취소');
      }
    };
  }, [like, currentUserUid, recipe]);
  • 좋아요 기능을 구현하고 훅으로 만들기 전에 코드를 써 봄.
  • useCallback과 useMemo는 함수를 메모이제이션하는데 사용됨.
    메모이제이션이란 동일한 계산을 반복해야 할 때 이전에 계산한 값들을 메모리에 저장해 동일 계산의 반복 수행을 최소화 하는 것을 말함. 이를 리액트에서는 useCallback과 useMemo 훅을 사용하여 적용하는 것.
  • 이 둘은 사용 방법과 용도가 조금 다르다.
  • useCallback메모이제이션된 함수를 반환하는 데 사용되며,
  • useMemo메모이제이션된 값을 반환하는 데 사용됨.
  • 두 훅 모두 종속성 배열을 사용하며 useCallback은 종속성 배열 내 값들이 변경될 때마다 새로운 함수를 생성하고 값이 변경되지 않을 때에는 이전에 생성된 함수 반환. useMemo는 마찬가지로 종속성 배열 내 값들이 변경될 때마다 연산을 실행, 값이 변경되지 않으면 이전에 계산된 값을 반환함.
  • 이를 통해 불필요한 함수 생성과 불필요한 연산을 실행하지 않아 불필요한 리렌더링을 방지함.
  • 주로 자식 컴포넌트로 전달되는 props 함수를 메모이제이션하기 위해 사용. 자식 컴포넌트가 불필요한 리렌더링을 방지할 수 있도록 사용함.
  • 위와 같은 이벤트 핸들러 함수 등은 컴포넌트가 리렌더링될 때마다 재생성되는데, 의존성 배열 내에 주입한 상태 값이 변경될 때에만 함수를 생성하여 이를 막을 수 있음.
  • 사용한다면 useCallback을 쓰겠지만 해당 코드에서는 실제 성능에 큰 영향이 없을 것 같아 사용하지 않을 것임.

추가

useCallback과 useMemo는 적용할만한 코드가 없었지만 react.memo를 적용할 컴포넌트를 찾음.

...

const RecipeCard = ({ recipe }: { recipe: Recipe }) => {
  const navigate = useNavigate();
  const handleCardClick = () => {
    navigate(`/detail/${recipe.id}`);
  };

  return (
    <>
      <RecipeCardWrapper onClick={handleCardClick}>
        <RecipeImgWrapper>
          <img src={recipe.image} />
        </RecipeImgWrapper>
        <RecipeTextWrapper>
          <p>{recipe.type}</p>
          <h1>{recipe.name}</h1>
        </RecipeTextWrapper>
      </RecipeCardWrapper>
    </>
  );
};

export default React.memo(RecipeCard);

...
  • React.memo()?
    앞서 알아본 useMemo(), useCallback() 훅과 달리 React.memo()는 함수형 컴포넌트의 렌더링 결과를 메모이제이션할 때 사용함. 이를 통해 해당 컴포넌트는 props가 변경되지 않는 한 리렌더링되지 않아 성능을 개선할 수 있음.
  • useMemouseCallback함수의 반환 값함수 자체메모이제이션하는 데 사용, React.memo함수형 컴포넌트의 렌더링 결과를 메모이제이션하는 데 사용.

번외

앞서 훅을 테스트했던, 레시피 찜 기능을 위해 만든 함수는 데이터 구조가 변경됨에 따라 아래와 같이 코드가 바뀌었다.

// 레시피 찜
  const handleLikeButtonClick = () => {
    // 로그인 체크
    if (!currentUserUid) {
      openAlert('로그인이 필요합니다.');
      return;
    }

    // 문서 참조
    const userRef = doc(dbService, 'users', currentUserUid);

    // 문서 데이터 가져오기
    getDoc(userRef)
      .then((userDoc) => {
        // 문서가 존재하면 기존 데이터에 레시피명 추가 또는 삭제
        if (userDoc.exists()) {
          const likes = userDoc.data()['user-likes'] || [];

          if (!like) {
            // 레시피 찜
            const updatedLikes = [
              ...likes,
              // 마이페이지 RecipeCard 출력에 필요한 정보들
              {
                id: recipe.id,
                type: recipe.type,
                name: recipe.name,
                image: recipe.image,
              },
            ];
            return updateDoc(userRef, { 'user-likes': updatedLikes });
          } else {
            // 레시피 찜 취소
            const updatedLikes = likes.filter(
              (item: any) => item.name !== recipe.name
            );
            return updateDoc(userRef, { 'user-likes': updatedLikes });
          }
        } else {
          // 문서가 존재하지 않으면 새 문서 생성 후 레시피명 추가
          const likes = [
            {
              id: recipe.id,
              type: recipe.type,
              name: recipe.name,
              image: recipe.image,
            },
          ];
          return setDoc(userRef, { 'user-likes': likes });
        }
      })
      .then(() => {
        setLike(!like);
        openAlert(like ? '찜 목록에서 삭제했어요.' : '레시피 찜 완료!');
      })
      .catch((error) => {
        console.error('레시피 찜에 실패했습니다.', error);
        openAlert('레시피 찜에 실패했습니다.');
      });
  };

  • 구조를 변경한 특별한 이유는 없고 늘 해왔듯 users-comments, users-likes 등의 컬렉션을 따로 구분해 사용하다가 그냥 보기 깔끔하게 정리를 하고싶었다.
  • 그렇게 코드를 변경하다보니 이와 같은 데이터 구조를 만들어버렸다.
  • 덕분에 GET, POST... 등의 코드가 길어져 가독성이 떨어진다.
  • 또한 users-likes 컬렉션에 들어가 한 번만 훑고 가져올 데이터를, users/user-likes 까지 한 번 더 들어가 가져와야한다. 고로 리소스가 더 소모된다.
  • 리팩토링 해야한다.
  • 공부하자 📋 데이터 모델링 개념 & ERD 다이어그램 작성 💯 총정리
profile
벨로그로 이사 중

0개의 댓글

Powered by GraphCDN, the GraphQL CDN