라이브러리 던지고 직접 구현하기 + 리팩토링

HR·2022년 12월 22일
0

오동동

목록 보기
6/7
post-thumbnail

에러의 시작

별점 시스템에 ionic-rating-component를 이용중이었다.
처음 설치 시에 패키지 충돌이 발생했지만, --legacy-peer-deps 설정을 통해 무시하고 지나쳤다.

뭐 하나를 설치할 때마다 에러가 발생했지만, 역시 --legacy-peer-deps옵션으로 넘어갔다.

빌드시에 또 다른 에러가 발생했지만, strict mode를 해제하면서 어물쩡 넘어갔다.

그렇게 쌓이고 쌓이다가 결국, ios 배포를 위한 빌드시에 곪은게 터지고 말았다.

문제 원인 분석

기존에 가져가 쓰던 ionic-rating-component의 앵귤러 버전과, 현재 프로젝트의 앵귤러 버전이 맞지 않아 생기는 이슈이다.
이를 해결하려면 라이브러리를 버리고 직접 구현해야 하겠다고 판단했다.

기존에 우선 사용하던 속성들을 먼저 확인했다.

<ionic-rating-component
  activeIcon="star"
  defaultIcon="star"
  halfStar="true"
  activeColor="var(--secondary_blue)"
  defaultColor="var(--middle_gray)"
  readonly="true"
  fontSize="1.25rem"
  rating="{{ editedRate }}"
  (ratingChanged)="onRatingChange($event)"
></ionic-rating-component>

요정도.. 얘네만 동일하게 속성 값을 받는 컴포넌트를 만들면 될 것 같다.


구현 내역

기존의 아이콘과, 별 색상 등은 미리 디자인된 소스를 사용할 것이므로 제외하고 컴포넌트를 작성했다.

1. readonly 속성

조건부로 css class를 할당할 수 있는 ngClass를 이용해, readonly가 true인 경우엔 비활성화 해주었다.

[ngClass]="readonly ? 'disabled' : null"
.disabled {
    pointer-events: none;
    cursor: default;
}

2. 별 크기 설정

아래와 같이 설정해놓은 뒤,

<ion-img
	#star
    ...
><ion-img/>

angular의 ViewChildren을 이용해 해당 별점의 참조값을 읽어온다.

@ViewChildren('star') starRef: QueryList<HTMLElement>;

그 후 별점 5개의 css 속성의 width값을 설정해 별들의 크기를 조정한다.

setStarSize() {
  this.starRef.map((starEl) => 
     starEl['el'].style.setProperty('width', this.starSize)
  );
}

3. 별점 개수

단순하게 Input으로 입력받는다.

@Input() rating: number;

...

updateRate(idx: number) {
  this.rating = idx + 1;

  for (let i = 0; i <= idx; i++) {
    this.stars[i] = 1;
  }
  for (let i = idx + 1; i < 5; i++) {
    this.stars[i] = 0;
  }

  this.changeDetectorRef.detectChanges();
  this.setStarSize();
}

이 때, 배열의 변경을 감지하기 위해 ChangeDetectorRef를 사용하였고, 배열의 값이 변경되면서 style 값이 초기화 되는 현상이 발생해 size를 다시 설정해주었다.

4. 별점 변화 감지

별점의 변화를 부모 컴포넌트에게 알려주기 위해서 OutputEventEmitter를 사용하였다.

@Output() ratingChanged = new EventEmitter<{ rate: number }>();

별점이 변화되면, 부모 컴포넌트에게 알려주기 위해 위의 ratingChanged에 rate 값을 담아 emit 해준다.

this.ratingChanged.emit({ rate: this.rating });

사용

부모 컴포넌트에서, 사용하는 방법은 아래와 같다.
우선 html에서는, 아래와 같이 컴포넌트를 선언한다.

<app-rating-component
    [readonly]="false"
    starSize="1.25rem"
    rating="3"
    (ratingChanged)="onRatingChange($event)"
></app-rating-component>

onRatingChange 함수에서, event 안의 rate 값으로 변화된 별점을 전달해주고 있으므로 아래와 같이 사용하면 된다.

onRatingChange(event) {
  console.log(event.rate);
}

결과

기존과 동일하게 사용이 가능하다. 짠!

기존에 npm으로 가져와 쓰던 것과 달리 기본으로 설정된 스타일도 없어져서, css 만지기도 쉬워졌고 패키지 버전들도 내 패키지 버전과 동일하기 때문에 충돌도 안생긴다.

답답하던 무언가가 뚫리는 기분이다. 라이브러리를 덜 쓰자는 다짐을 오늘도 다시 하게 된다.

갈때도 구질구질한 친구야 잘가... 다신 보지말자


전체 코드가 담긴 깃허브는 여기를 누르면 이동할 수 있습니다!


+23.01.21 추가 : 리팩토링

기존에 별점을 입력받고, 별점에 따라 화면에 별점 개수를 렌더링 해주는 함수 로직은 다음과 같다.

public stars = [0, 0, 0, 0, 0];

updateRate(idx: number, initial?: boolean) {
  ...
  
  for (let i = 0; i <= idx; i++) {
    this.stars[i] = 1;
  }
  for (let i = idx + 1; i < 5; i++) {
    this.stars[i] = 0;
  }
  
  ...
}

쉬운 알고리즘이지만, 직관적이지 않고 for문 2개의 역할을 한번 더 생각해봐야 했다. 또한 나중에 0.5개와 같은 옵션이 생길 경우에는 for문 내부의 로직이 더 길어지며, 가독성이 더욱 좋아지지 않을 것이라고 예상됐다.

이를 고민하다가 오동동 backend 팀원들의 토론을 듣다가 map에 대한 이야기를 하는 것을 듣고, 아이디어가 떠올랐다.

이를 통해 아래와 같이 리팩토링 했다.

  1. 우선 별점 개수를 입력받고, 입력 받은 별점 개수를 통해 별점 정보를 업데이트 하는 함수를 분리하였다.
public stars = [0, 0, 0, 0, 0];

setStars(rating: number) {
  const starsMap = {
    0: [0, 0, 0, 0, 0],
    1: [1, 0, 0, 0, 0],
    2: [1, 1, 0, 0, 0],
    3: [1, 1, 1, 0, 0],
    4: [1, 1, 1, 1, 0],
    5: [1, 1, 1, 1, 1],
  };

  this.stars = starsMap[rating];
}
  1. 기존 함수에는, 1번에서 생성한 함수에 별점 정보를 전달만 해주는 것으로 변경하였다.
updateRate(idx: number, initial?: boolean) {
  ...
  
  this.setStars(this.rating);

  ...
}

기존보다 훨씬 더 가독성이 좋아졌고, 0.5점과 같은 정보가 추가되어도 starsMap에 데이터만 추가해 주면 되므로, 확장하기에도 용이해 졌다!

1개의 댓글

comment-user-thumbnail
2022년 12월 22일

글 잘쓰시네요~

답글 달기