본문 바로가기
Library-Framework/React

🌟 외부 라이브러리 없이 별점 평점 기능 만들기

by 그랴 2023. 12. 29.

별점 평점 은 리뷰 기능을 만들 때 빠질 수 없는 요소이다.

 

현재 진행 중인 프로젝트의 리뷰 작성 페이지를 만들면서 별점 평점 기능을 만들게 되었다.

요새 Chakra UI 처럼, 외부 UI 라이브러리가 매우 잘 되어있기 때문에 자주 쓰이는 UI 컴포넌트들은 대부분 라이브러리를 사용해 만들고 나는 로직에 더 집중하는 방식으로 개발을 진행했었다.

 

하지만 아쉽게도 (?)  자주 쓰는 라이브러리에서는 별점 컴포넌트가 제공되지 않았고

로직을 생각해보니 그리 어렵지 않은 것 같아서 직접 만들어보았다.

 

(찾아보니 있긴 했다.)

https://www.npmjs.com/package/rc-rate

 

rc-rate

React Star Rate Component. Latest version: 2.12.0, last published: 7 months ago. Start using rc-rate in your project by running `npm i rc-rate`. There are 410 other projects in the npm registry using rc-rate.

www.npmjs.com

 


설계

    • 초기값 : 5점
    • 최소값 : 1점
    • 최대값 : 5점
  • 주요 로직
    • 별을 클릭 시, 그보다 상위 점수를 표시하는 별은 비활성화되어야 하고 하위 점수를 표시하는 별은 활성화되어야 한다.
    • 각 별마다 useState를 사용해 활성화 상태를 관리해준다.
  • 별 이미지 교체 방식
    • svg 파일을 2개 사용한다.
      • ic-star-on.svg
      • ic-star-off.svg
    • 객체를 이용해 true와 on, false와 off를 각각 치환해준다.

 


 

초기 코드

import { useEffect, useState } from "react";

const Star = () => {
  const [rateScore, setRateScore] = useState(0);
  const [star1, setStar1] = useState(false);
  const [star2, setStar2] = useState(false);
  const [star3, setStar3] = useState(false);
  const [star4, setStar4] = useState(false);
  const [star5, setStar5] = useState(false);

  const icList: { [key: string]: string } = {
    true: "on",
    false: "off",
  };

  const clickStar = (starScore: number) => {
    if (starScore === 1) {
      setRateScore(1);
      setStar1(true);
      setStar2(false);
      setStar3(false);
      setStar4(false);
      setStar5(false);
    } else if (starScore === 2) {
      setRateScore(2);
      setStar1(true);
      setStar2(true);
      setStar3(false);
      setStar4(false);
      setStar5(false);
    } else if (starScore === 3) {
      setRateScore(3);
      setStar1(true);
      setStar2(true);
      setStar3(true);
      setStar4(false);
      setStar5(false);
    } else if (starScore === 4) {
      setRateScore(4);
      setStar1(true);
      setStar2(true);
      setStar3(true);
      setStar4(true);
      setStar5(false);
    } else if (starScore === 5) {
      setRateScore(5);
      setStar1(true);
      setStar2(true);
      setStar3(true);
      setStar4(true);
      setStar5(true);
    }
  };


  return (
    <MainWrapper>
      <StarWrapper>
        <img
          onClick={() => clickStar(1)}
          className="star"
          src={`/assets/svg/ic-star-${icList[star1.toString()]}.svg`}
          alt="star"
        />
        <img
          onClick={() => clickStar(2)}
          className="star"
          src={`/assets/svg/ic-star-${icList[star2.toString()]}.svg`}
          alt="star"
        />
        <img
          onClick={() => clickStar(3)}
          className="star"
          src={`/assets/svg/ic-star-${icList[star3.toString()]}.svg`}
          alt="star"
        />
        <img
          onClick={() => clickStar(4)}
          className="star"
          src={`/assets/svg/ic-star-${icList[star4.toString()]}.svg`}
          alt="star"
        />
        <img
          onClick={() => clickStar(5)}
          className="star"
          src={`/assets/svg/ic-star-${icList[star5.toString()]}.svg`}
          alt="star"
        />
      </StarWrapper>
    </MainWrapper>
  );
};

export default Star;

리팩터링

개발일지를 정리하며 코드를 옮겨두고 보니, 별점을 관리하는 clickStar 함수의 길이가 너무 길었다. 비슷한 형태의 로직이 의미 없이 반복되고 있었기 때문이다. 이를 개선하기 위해 별점 이미지에서 클릭 이벤트 발생 시 id 값을 전달하고, 별점을 관리하는 함수들을 배열에 담아 이를 순회하게 하였다. 클릭된 별의 id 이하의 별은 활성화, 초과의 별은 비활성화하게 하였다. 이를 통해 불필요하게 길었던 함수를 더 짧고 명확하게 개선할 수 있었다.

const clickStar = (e: any) => {
    const starID = e.target.id;
    setRateScore(Number(starID));

    const funcList = [setStar1, setStar2, setStar3, setStar4, setStar5];

    for (let i = 0; i < 5; i++) {
      if (i < +starID) funcList[i](true);
      else funcList[i](false);
    }
  };

완성 화면