리액트로 리뷰 별점(Rating) 구현하기(소수점,클릭했을때)

react
블로그 이미지

이챙(leechaeng)

﹒2024. 7. 26.

이번에 리액트로 별점(Rating) 기능을 구현해 보았다.

 

✅ 요구사항

1. 별 5개중에서 아무 순서를 클릭 했을때 별점이 그 순서만큼 채워져야함. 5개의 별점중 3번째 별 클릭시 별점 3점이 된다.
2. 별점이 소수점일시(4.8 등) 별이 소수점에 맞게 채워져야함.

 

 

요구사항 1번 부터

import { useState } from 'react';
import { FaStar } from 'react-icons/fa6';

interface IStarProps {
    w: string;
    h: string;
    readonly: boolean;
    rate?: number;
}

export default function Star({ w, h, readonly, rate }: IStarProps) {
    const [rating, setRating] = useState(rate || 0);

    return (
        <div className={`flex`}>
            {Array.from({ length: 5 }).map((_, index) => (
                <div
                    className={`relative ${w} ${h} cursor-pointer`}
                    key={index}
                >
                    <FaStar
                        className={`${w} ${h} text-gray200`}
                    />
                </div>
            ))}
        </div>
    );
}


Star라는 컴포넌트로 요구 사항 1,2번을 충족하기 위해 공통으로 사용 되게 끔 구현했다.
props를 줌으로써 유동적인 컴포넌트로 만들었다.
rating state값은 rate가 있을 경우 초기값이 rate가 되고 없으면 0이 되도록 했다. rate가 있다는 것은 readonly일 경우 이므로 별점이 초기값이 되어야 하기 때문.

interface IStarProps {
    w: string;
    h: string;
    readonly: boolean;
    rate?: number;
}

 

  • w: width 값
  •  h: height 값
  •  readonly: 요구사항1은 readonly가 false이고 요구사항2은 readonly가 true다.
  •  rate: 별점 값이 있을 경우 내보냄

 

<Star w={'w-10'} h={'h-10'} readonly={false} />


요구 사항 1번은 위와 같이 컴포넌트를 불러왔다.

 

export default function Star({ w, h, readonly, rate }: IStarProps) {
    const [rating, setRating] = useState(rate || 0);

    const handleClickStar = (index: number) => {
        if (!readonly) {
            setRating(index + 1);
        }
    };

    return (
        <div className={`flex`}>
            {Array.from({ length: 5 }).map((_, index) => (
                <div
                    className={`relative ${w} ${h} cursor-pointer`}
                    key={index}
                >
                    <FaStar
                        onClick={() => handleClickStar(index)}
                        className={`${w} ${h} ${!readonly && rating >= index + 1 ? 'text-primary-review' : 'text-gray200'}`}
                    />
                </div>
            ))}
        </div>
    );
}


이제 클릭했을때 코드를 추가해보자.
클릭했을때 해당 별의 index를 넘겨주면 rating의 값에 현재 별점의 값을 등록할 수 있다.
3번째 별을 클릭하면 rating은 3이 된다.

그에맞게 스타일을 바꿔주면 된다.
만약 rating이 3이고 index + 1이 5면 3번째 별까지만 별점 색을 입혀주고 그외에 별들은 gray색으로 되도록 했다.
계산한것을 결과로 보면 이렇다
1번째별(index + 1) = true
2번째별(index + 1) = true
3번째별(index + 1) = true
4번째별(index + 1) = false
5번째별(index + 1) = false

별점 구현

완벽하다!!!! 💛💛

 

 

 

요구사항 2번

리뷰가 딱 떨어지는 정수면 구현이 쉽겠지만.. 소수점일 경우 그 만큼 채워지는 별점을 구현하기를 원했다.
예를들어 4.8이면 별이 4개가 채워지고 나머지 한개는 80%로 채워지는...?

지그재그 소수점 별점

지그재그 별점이 딱 내가 원하는 별점이였다.
그래서 지그재그의 별점 구현을 따라해서 구현해봤다.

 

지그재그 별점 코드

대충 보아하니 소수점의 별점일 경우에는 별 하나만 퍼센트로 채워지게끔 되어 있었따.
별 아이콘에 span 태그를 absolute 시켜서 60%만큼 리뷰 컬러를 채워지게 끔 만든거 같다.


{Array.from({ length: 5 }).map((_, index) => (
    <div
        className={`relative ${w} ${h} cursor-pointer`}
        key={index}
    >
        <FaStar
            onClick={() => handleClickStar(index)}
            className={`${w} ${h} ${!readonly && rating >= index + 1 ? 'text-primary-review' : 'text-gray200'}`}
        />
        {readonly && (
            <span
                style={
                    rate
                        ? {
                              width: calculateRate(
                                  rating,
                                  index + 1,
                              ),
                          }
                        : {}
                }
                className={`${h} absolute left-0 top-0 overflow-hidden`}
            >
                <FaStar
                    className={'h-full w-auto text-primary-review'}
                />
            </span>
        )}
    </div>
))}


readonly일 경우 첫번째 FaStar가 gray색이 되니 span태그를 absolute시키고 해당 별의 rating값을 계산해서 퍼센테이지 만큼 리뷰 컬러를 입히도록 했다.
중요한 점은 span태그를 oveflorw-hidden을 시키고 height값을 주면 퍼센테이지 만큼 컬러가 입혀진다.

 

    const calculateRate = (rate: number, index: number) => {
        if (rate >= index) {
            return '100%';
        }
        if (Math.floor(index - rate) > 0) {
            return '0%';
        }
        const percentage = ((rate % 1) * 100).toFixed();
        return `${percentage}%`;
    };


rate가 3.6이고 index가 4라고 할경우
rate가 index 1,2,3보다 크기 때문에 100%로 별에 리뷰 컬러가 채워진다.
index 4는 percentage로 계산이 되어 60%를 리턴할 것이고 index 5는 두번째 조건문에서 걸리기 때문에 0프로를 리턴한다.
이제 완성된 컴포넌트를 사용해보자

 

<Star w={'w-10'} h={'h-10'} readonly rate={3.6} />


이 props로 구성된 컴포넌트를 렌더링 하면 아래와 같이 나온다.

리액트 리뷰 별점 구현 완성

 

clip-path를 사용하거나 좀더 쉽게 코드로 구현하는 방법이 있는거 같은데 다음에 리팩토링 할 시간이 생기면 구현을 해봐야겠따!!!!!

 

 

 

전체코드

import { useState } from 'react';
import { FaStar } from 'react-icons/fa6';

interface IStarProps {
    w: string;
    h: string;
    readonly: boolean;
    rate?: number;
}
export default function Star({ w, h, readonly, rate }: IStarProps) {
    const [rating, setRating] = useState(rate || 0);

    const handleClickStar = (index: number) => {
        if (!readonly) {
            setRating(index + 1);
        }
    };

    const calculateRate = (rate: number, index: number) => {
        if (rate >= index) {
            return '100%';
        }
        if (Math.floor(index - rate) > 0) {
            return '0%';
        }
        const percentage = ((rate % 1) * 100).toFixed();
        return `${percentage}%`;
    };

    return (
        <div className={`flex`}>
            {Array.from({ length: 5 }).map((_, index) => (
                <div
                    className={`relative ${w} ${h} cursor-pointer`}
                    key={index}
                >
                    <FaStar
                        onClick={() => handleClickStar(index)}
                        className={`${w} ${h} ${!readonly && rating >= index + 1 ? 'text-primary-review' : 'text-gray200'}`}
                    />
                    {readonly && (
                        <span
                            style={
                                rate
                                    ? {
                                          width: calculateRate(
                                              rating,
                                              index + 1,
                                          ),
                                      }
                                    : {}
                            }
                            className={`${h} absolute left-0 top-0 overflow-hidden`}
                        >
                            <FaStar
                                className={'h-full w-auto text-primary-review'}
                            />
                        </span>
                    )}
                </div>
            ))}
        </div>
    );
}

 

이챙(leechaeng)
이챙(leechaeng)

프론트엔드 개발도 하고 뛰기도 하고

'react' 카테고리의 관련 글