이챙의 개발 log
리액트로 리뷰 별점(Rating) 구현하기(소수점,클릭했을때)
이번에 리액트로 별점(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>
);
}
'react' 카테고리의 다른 글
리액트로 마우스 드래그 가로 스크롤 구현하기 (+합성 컴포넌트로 재사용 높이기) (1) | 2024.09.19 |
---|---|
useState는 비동기적으로 동작한다! (1) | 2024.04.30 |
framer-motion으로 리액트에서 애니메이션을 쉽게 ✨ (0) | 2024.04.19 |
useTransition으로 성능 개선하기 (0) | 2024.02.28 |
react+webpack+tailwindcss 설치하기! (0) | 2022.09.19 |
이챙(leechaeng)
프론트엔드 개발도 하고 뛰기도 하고