왓챠피디아의 평점기능을  CU편의점에 접목시킨 프로젝트 watCU가 끝이 났습니다.

 

제가 담당한 부분은 Detail(상품 상세 페이지)와 Star(별점/별점그래프)이였습니다.

 

그 기념으로 기억에 남는 기능과 느낀점을 간단히 적어볼까 합니다.

 

 

기억에 남는 기능 소개

 

 

 

<Slider>

 

React로 제대로 된 기능을 만들어 보는건 슬라이더 기능이 처음이었습니다.

구글로 검색해 본 대부분의 결과는 라이브러리로 만든 결과물이 대부분이었고 간혹가다 있는 라이브러리 없이 만든 영상은

function으로 만든 영상이였습니다.

 

그래서 저는 바닐라 JS로 해당 기능을 구현하는 영상을 보고 그것을 React로 옮겨야겠다고 생각했습니다.

그 과정에서 수많은 고뇌와 오류를 맞이했지만 끝끝내 그것을 구현하는데 성공했습니다.

 

그 후 멘토님에게 리뷰를 받고 리팩토링 과정을 거치며 코드를 한층 더 간결하게 작성할 수 있었습니다.

 

before

handleNextSlider = () => {
    const sliderElement = this.sliderRef.current;
    const itemElement = this.itemRef.current;
    const itemWidth = Math.ceil(itemElement.getBoundingClientRect().width);
    if (
      this.state.transFormPosition ===
      (sliderElement.getElementsByClassName('anotherItem').length - 4) *
        -itemWidth
    ) {
      return;
    }

    this.setState(
      {
        transFormPosition: this.state.transFormPosition - itemWidth,
      },
      () => {
        sliderElement.style.transform = `translate(${this.state.transFormPosition}px)`;
      }
    );
  };

 

after

  handleNextSlider = () => {
    const sliderElement = this.sliderRef.current;
    const { currentIndex } = this.state;
    const { subImage } = this.props;

    if (this.itemRef.current) {
      const itemWidth = Math.ceil(
        this.itemRef.current.getBoundingClientRect().width
      );
      if (currentIndex === subImage - 4) {
        return;
      }

      this.setState(
        {
          currentIndex: currentIndex + 1,
        },
        () => {
          sliderElement.style.transform = `translate(-${
            this.state.currentIndex * itemWidth
          }px)`;
        }
      );
    }
  };

getBoundingClientRect().width 매서드를 사용하여 일부러 px를 지정하지 않아도 그 너비에 맞게

슬라이드의 요소가 넘어가도록 설정하였습니다.

 

그리고 Refactoring을 통해서 복잡한 계산식을 통하여 슬라이더의 위치를 계산하는 대신 Index 정보를 이용하여 보기 쉽고

직관적이도록 변경하였습니다.

 

 

별점/별점그래프

<Star>

 

<StarGraph>

 

 

별점 기능과 별점그래프를 실시간으로 연동시키는 것은 그 과정은 어려웠지만 하나하나 원하는 대로 코드가 구현될 때 마다

매우 즐거웠습니다. 

난생 처음으로 백엔드에게 데이터를 받고 또 post를 이용하여 보내고 그 과정이 그래프라는걸 통해서 유의미한 결과물이 나오는 것은 

 

'아, 내가 정말로 백엔드와 통신을 하는중이구나!' 

 

라는 감상이 들게 만들게 만들었습니다.

제가 실시간 연동을 한 방법은 다음과 같았습니다.

 

<Detail.js(부모컴포넌트)>

export default class Detail extends Component {

  constructor() {
    super();
    this.state = {
      userInfo: [], 
    };
  }

callStarGraphApi = () => {
    fetch(
      `${BASE_URL}/ratings/products/${this.props.match.params.productId}/graph`
    )
      .then(res => res.json())
      .then(data =>
        this.setState({
          userInfo: data.results,
        })
      );
  };

componentDidMount = () => {
    this.callStarGraphApi();
  };

render(){
  return{
    <StarRating callStarGraphApi={this.callStarGraphApi}>                  
    <StarGraph userInfo={this.state.userInfo} />
  }
}

처음 rating에 대한 정보를 컴디마를 통하여 userInfo state에 저장합니다.

그 다음 자식컴포넌트들인

StarRating에 Api호출 함수 callStarGraphApi를 props로 주고

StarGraph에 userInfo를 props로 줍니다.

 

 

<StarRating.js>

export default class StarRating extends Component {
  constructor() {
    super();
    this.state = {
      rateValue: [false, false, false, false, false]
    };
  }


handleStarClick = clickedIndex => {
    const prevRateValue = [...this.state.rateValue];
    const isClickedStarActive = prevRateValue[clickedIndex];
    const isNextStarActive = prevRateValue[clickedIndex + 1];

    if (isClickedStarActive && isNextStarActive) {
      prevRateValue.fill(false, clickedIndex + 1);
    } else if (isClickedStarActive) {
      prevRateValue.fill(false, 0, clickedIndex + 1);
    } else if (!isClickedStarActive) {
      prevRateValue.fill(true, 0, clickedIndex + 1);
    }

    const rating = prevRateValue.filter(value => value).length;

 
      fetch(`${BASE_URL}/ratings/products/${this.props.id}`, {
        method: 'POST',
        body: JSON.stringify({
          rating: rating,
        }),
        headers: {
          'Content-type': 'application/json; charset=UTF-8',
          Authorization: localStorage.getItem('token'),
        },
      }).then(() => {
        const { callStarGraphApi} =this.props;

        callStarGraphApi();
      });

      this.setState({
        isHover: false,
        hoverRateValue: [false, false, false, false, false],
        rateValue: prevRateValue,
      });
    
  };

별점을 클릭시 (여기서는 3점을 줬다고 하겠습니다.)

[true, true, true ,false ,false]가 나오게 됩니다.

그리고 true 갯수인 3을 fetch함수에서 post로 데이터를 보내게 됩니다.

 

 

이렇게 되면 기존 데이터서버에는 3점이라는 데이터가 하나 더 쌓이게 됩니다.

하지만 아쉽게도 우리는 쌓이기 전의 데이터를 받아 온 상태입니다.

최신상태의 데이터가 아니라는 것입니다.

그러니 다시 이 데이터를 호출해 줄 필요가 있습니다.

 

아까 Detail.js에서 받아온 Api호출 함수 callStarGraphApi를 호출해줍니다.

 

.then(() => {
        const { callStarGraphApi} =this.props;

이제 데이터가 최신상태로 업데이트 됩니다.

이렇게 되면 Detail.js의 this.state.userInfo역시 최신상태로 업데이트 됩니다.

 

 

<StarGraph.js>

export default class StarGraph extends Component {
  render() {
    const ratingValue = this.props.userInfo.map(user => user.rating);

    const totalPerson = ratingValue.filter(ratingPerson => ratingPerson).length;
    const percentageList = ratingValue
      .reduce(
        (preValue, ratingPerson) => {
          if (ratingPerson === 0) {
            return preValue;
          }

          preValue[ratingPerson - 1] += 1;
          return preValue;
        },
        [0, 0, 0, 0, 0]
      )
      .map(ratingPerson => Math.round((ratingPerson / totalPerson) * 100));

    const maxPercentage = Math.max(...percentageList);

    return (
      <>
        <div className="graphLayout">
          <div className="graphContainer">
            {percentageList.map((percent, index) => {
              return (
                <div
                  className={
                    percent >= maxPercentage
                      ? 'maxRatingPerson'
                      : 'ratingPerson'
                  }
                  key={index}
                  style={
                    percent > 0 ? { height: `${percent}%` } : { height: '1%' }
                  }
                ></div>
              );
            })}
          </div>

          <ul className="scoreNumber">
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
            <li>5</li>
          </ul>
        </div>
      </>
    );
  }
}

StarGraph는 reduce함수를 이용하여 각 별점의 평가자 수를 반영하도록 하였습니다.

그 다음 각 별점의 평가자 수를 전체 평가자의 수로 나눈 후 곱하기 100을 하여 백분율로 나타낸 겁니다.

 

이제 예제를 통해 설명해보겠습니다.

 

별점 1점: 2명 → 약22%

별점 2점: 1명 → 약11%

별점 3점: 2명 → 약22%

별점 4점: 2명 → 약22%

별점 5점 : 2명 → 약22%

 

총 9명이 다음과 같이 평가하였습니다.

이때 유저A가 평점을 3점을 새로이 추가했다고 합시다.

그러면 실시간 업데이트된 데이터가 StarGraph로 내려옵니다.

그렇다면...

 

별점 1점: 2명 → 20%

별점 2점: 1명 → 10%

별점 3점: 3명 → 30%

별점 4점: 2명 → 20%

별점 5점 : 2명 → 20%

 

이렇게 그래프가 변합니다.

이제 이렇게 변하는 그래프에 반응하도록 CSS로 애니메이션까지 주면 완성입니다.

 

 

프로젝트 후 느낀점

 

 

 

watCU는 제가 다른 사람들과 '협업'을 통해 완성한 최초의 프로젝트입니다.

그렇기 때문인지 그 완성본을 봤을 때의 신기함과 기쁨은 정말 낯선 것이었습니다. 

 

본래 모든 일을 혼자서 처리하는 것을 선호했기 때문에 다른 사람을 믿고 일을 맡기는 건 처음엔 불안하고 걱정되기 마련이었습니다.

그러나 점차 시간이 지나고 프로젝트의 형태가 갖춰지면서

 

제가 지녔던 생각은 너무나도 편협하고 좁은 것이라는 것을 깨달을 수 있었습니다. 

 

모두 자기가 맡은 바에 충실하며 기한을 지키기 위해 애쓰고 혼자서 끙끙대며 앓아가며 코드를 작성하는 것보다는 서로 협력하며 알고 있는 지식은 베풀고 모르는 지식은 가르침 받는 문화는 너무나도 신선하고 충격적이였습니다.

 

그리고 끝끝내 많은 고뇌와 조언으로 탄생한 우리의 프로젝트를 다같이 모여서 보는 순간 

 

이 프로젝트는 '내가 만든 프로젝트'가 아닌 '우리가 만든 프로젝트'라는게 마음 속에 깊이 새겨졌습니다.

 

앞으로 개발자로 살아가게 되면서 가장 걱정했던 것은 협업이였지만

 

이제는 개발자로 살아가게 되면서 가장 기대되는 것이 협업으로 변하게 된 정말 인상깊은 프로젝트였다고 생각합니다.

 

 

 

 

 

 

 

 

 

 

'Wecode > Project' 카테고리의 다른 글

[Project-1] watCU - 슬라이더  (0) 2021.09.12
[Project-1] watCU - 별점,별점그래프  (0) 2021.09.12

+ Recent posts