React Storybook 리뷰 별점 컴포넌트 만들기

지원 ·2024년 4월 30일

스토리북을 이용한 컴포넌트 제작

웹/앱에서 흔히 보이는 별점 리뷰 컴포넌트를 구현해서 스토리북에 추가해보겠습니다. 그리고 스토리북을 이용해 컴포넌트를 사용하는 방법을 보여드리겠습니다.

최소한 코드로 리뷰 이미지 출력을 어떻게 할 있을까?

가장 먼저 떠오른 것은 순회하는 방법이었습니다. 별점 5점부터 1점까지 배열을 먼저 생성하고, 배열에 있는 요소를 하나씩 순회하면 1인 경우 원하는 <img> 요소를 리턴하고 그리고 아닌 경우에도 원하는 <img> 요서를 리턴하는 방식을 생각했습니다.

const fiveStarRating = [1,1,1,1,1]
const fourStarRating = [1,1,1,1,0]
const threeStarRating = [1,1,1,0,0]
const twoStarRating = [1,1,0,0,0]
const oneStarRating = [1,0,0,0,0]
const noRatings = [0,0,0,0,0]

컴포넌트 코드

import star from "../../assets/star-review.png";
import emptyStar from "../../assets/empty_star_icon.svg";

interface ReviewRatingProp {
  reviewRating?: number[];
}

export const ReviewRating: React.FC<ReviewRatingProp> = ({ reviewRating }) => {
  return (
    <div className="flex items-center gap-[2px]">
      {reviewRating?.map((i) =>
        i === 1 ? (
          <img src={star} alt="yellow star" className="size-4" />
        ) : (
          <img src={emptyStar} alt="empty star" className="size-4" />
        )
      )}
    </div>
  );
};

별점마다 하나씩 컴포넌트를 구현하지 말고, 스토리북으로 컴포넌트를 만들기

공식문서를 참고해서 기본적인 셋팅이 완료된 코드입니다. 참고로 파일은 컴포넌트로 사용하기 때문에 ____.stories.ts 사용하시면 안돼고, _____.stories.tsx를 사용하셔야 됩니다.

export default {
  title: "Ratings", // 스토리북 실행했을 때 나타나는 제목
  component: ReviewRating, // 컴포넌트 추가
  tags: ["autodocs"], // docs 생성
  parameters: {
    layout: "centered", // 요소를 화면 중간에 배치
  },
} as Meta;

const Template: StoryFn<typeof ReviewRating> = (args) => (
  <ReviewRating {...args} />
);

기본적이 세팅이 되었으면 별점 컴포넌트를 만들어볼까요?

처음에 생각했던 배열을 인자로 컴포넌트의 props (args)로 넘겨서 배열에 요소가 1인 경우에 노란색 별 이미지를 출력하고, 0인 경우 그레이색 별을 출력하는 방식으로 진행했습니다.

이제 데이터를 호출하고 사용자의 리뷰 점수에 따라 컴포넌트를 구현하면 될 것 같네요!

const userDetail  = [
{id: 1, user: 'Bob', review: 5}, 
{id: 1, user: 'Sally', review: 2}, 
{id: 1, user: 'Jelly', review: 3}
]
export const FiveStars = Template.bind({});
FiveStars.args = {
  reviewRating: [1, 1, 1, 1, 1],
};

export const FourStars = Template.bind({});
FourStars.args = {
  reviewRating: [1, 1, 1, 1, 0],
};

export const ThreeStars = Template.bind({});
ThreeStars.args = {
  reviewRating: [1, 1, 1, 0, 0],
};

export const TwoStars = Template.bind({});
TwoStars.args = {
  reviewRating: [1, 1, 0, 0, 0],
};

export const OneStar = Template.bind({});
OneStar.args = {
  reviewRating: [1, 0, 0, 0, 0],
};

export const NoReviews = Template.bind({});
NoReviews.args = {
  reviewRating: [0, 0, 0, 0, 0],
};
<>
{userDetail.map((data) => {
	if(data.review === 5) {
    	return <ReviewRating {...FiveStars.args} />
    } else if (data.review === 4) {
    	return <ReviewRating {...FourStars.args} />
    } else { // 리뷰가 0인경우
    	return <ReviewRating {...NoReviews.args} />
    }
    //이런식으로 이어서 하면 됩니다!
 })}
</>

0개의 댓글