[Vue]Vuex의 state는 mutations만으로 변경한다.

이은진·2021년 5월 1일
1

Vue

목록 보기
1/2

쿠폰을 리스트별로 중복으로 선택하는 기능을 구현하면서 mutations와 lodash의 소중함을 배웠다.

1. 문제상황

기존의 쿠폰적용 페이지는 다음과 같다.

기존에 예약페이지에서 접근하는 쿠폰적용 페이지에는 UI상 쿠폰의 종류가 한 개밖에 없어 리스트도 하나였는데, 이번에 일반쿠폰과 중복쿠폰 리스트를 따로 구성하고 그에 따라 디자인과 구조에 변동이 생기게 되었다. 일례로 기존에는 input 라디오 버튼을 클릭하면 e.target 값을 가지고 쿠폰 상태를 변경하여 store에 넘겨주었지만 이번에는 디자인 팀에서 새로 lottie 파일을 주셔서 클릭 시에 또하나의 컴포넌트를 애니메이션으로 실행하는 방식으로 변경하게 되었다.

디자인팀에서 원한 방식은 쿠폰 리스트에서 클릭한 쿠폰만 파란색으로 변하고 나머지는 디폴트 형태로 만드는 것이다. 일단 전혀 어려울 것이 없을 거라고 생각하고 바로 개발에 착수했지만, 생각보다 여러 모듈을 변경하고 보수해야 해서 시간이 다소 소요되었다. 쿠폰을 셋팅하는 기능 자체가 중복쿠폰이 있을 거라고 예상하고 짜여진 게 아니었고, 이미 그 상태로 온갖 파일에 메서드를 각각 모듈화해놓으신 기존 개발자 분의 코드에 익숙해지는 과정에서 시간이 오래 걸렸다. 단순히 클릭기능과 컴포넌트 분리 변경보단 할 일이 많았다.

2. 내가 단순하게 생각하고 접근한 방법

코드를 심플하게 변경해서 기록해보았다.

// CouponCardList.vue

// template
<ul v-if="isListOpen">
  <CouponCard
    v-for="coupon in couponList"
    :key="coupon.id"
    :coupon="coupon"
    @chooseCoupon="chooseCoupon"
  />
</ul>

// script
chooseCoupon(selectedCouponId) {
  const chosenList = this.couponList.map((coupon) => {
    coupon.selected = coupon.id === selectedCouponId;
    return coupon;
  });
  this.$emit('chooseCoupons', chosenList)
},

일반쿠폰, 중복쿠폰 리스트 컴포넌트(부모)에서 chooseCoupon 메서드를 정의하였다. 이 chooseCoupon 메서드는 개별 쿠폰 컴포넌트(자식)의 속성으로 넘겨주어, 자식을 클릭하면 부모의 상태가 변하도록 하였다. 이 chooseCoupon 에서는 클릭하여 변경된 쿠폰 리스트를 가지고, 쿠폰페이지 컴포넌트(부모의 부모) 에서 정의한 chooseCoupons 메서드를 emit하면서 넘겨준다. 쿠폰페이지 컴포넌트에서는 기존에 짜여진 쿠폰 및 포인트 계산 등을 하는 메서드를 한꺼번에 실행시키는 '적용하기' 버튼이 있다.

// CouponCard.vue

// template
<li @click="select"> ... </li>
          
// script
select() {
  this.$emit('chooseCoupon', this.coupon.id);
},

쿠폰 카드 컴포넌트(자식)을 선택했을 때 메서드를 emit 하면서 쿠폰 id를 넘겨준다. 이렇게 쿠폰리스트를 돌면서, 선택한 자식 쿠폰의 아이디와 일치하는 아이디를 가진 원소의 속성을 변경시켜주는 방식이다. selected: true, 아니면 selected: false 로 만들어서 기존 리스트 데이터와 바꿔주면 끝나는 거라고 생각했다. 그리고 기능도 정상적으로 잘 돌아갔다.

3. 내 방식의 문제점

// CouponCardList.vue

// script
chooseCoupon(selectedCouponId) {
  const chosenList = this.couponList.map((coupon) => {
    coupon.selected = coupon.id === selectedCouponId;
    return coupon;
  });
  // this.$emit('chooseCoupons', chosenList)
},

그런데 우연히 chooseCoupons 메서드를 쿠폰페이지로 emit하는 줄을 주석처리하게 되었는데 이상한 점을 발견하였다. chosenList 변수에 기존 couponList를 map 돌면서 값을 변경하여 담아줬는데, 그 chosenList 를 couponList와 바꿔주지도 않았는데도 클릭 기능이 잘 동작하는 것이다. 정말 이 기능이 도대체 왜 작동하는지 모르겠어서 오랫동안 머리를 싸맸는데 알고 보니까 state를 map으로 변경을 시키면 mutation을 통해서 변경시킨 것이 아니기 때문에 비정상적으로 원본 state를 변형시키는 거라고 했다. 애초에 map 메서드가 기존 원본 배열을 변경하는 게 아니라 참조만 하는 메서드라고 알고 있었기에 그런 문제가 생길 줄 상상도 못했다.

4. 정석대로 mutations를 써서 해결한 방법

// CouponCardList.vue
chooseCoupon(selectedCoupon) {
  const chosenList = cloneDeep(this.couponList).map((coupon) => {
    coupon.selected = coupon.id === selectedCoupon.id;
    return coupon;
  });
  const couponListInfo = {
    list: chosenList,
    duplicated: selectedCoupon.duplicationFlag,
  };
  this.$store.commit(CHOOSE_COUPON, couponListInfo);
},
// mutations.js
[CHOOSE_COUPON](state, couponListInfo) {
  if (couponListInfo.duplicated) {
    state.couponList = [
      ...state.couponList.filter((coupon) => !coupon.duplicationFlag),
      ...couponListInfo.list,
    ];
  } else {
    state.couponList = [
      ...state.couponList.filter((coupon) => coupon.duplicationFlag),
      ...couponListInfo.list,
    ];
  }
},

직접 받아온 couponList에 바로 map메서드를 사용하는 것이 아니라, lodash를 사용해서 쿠폰 리스트 상태를 다른 변수에 딥카피 해놓고, 그 변수를 가지고 객체를 하나 만들어, mutations에서 정의해 놓은 메서드를 commit했다. 객체를 만든 이유가 일반쿠폰과 중복쿠폰 두 가지 쿠폰 리스트가 있어, 각각의 경우에 따로 선택을 할 수 있도록 flag를 한 개 더 넘겨줘야 했기 때문이다.
일단 cloneDeep만으로도 원본 배열을 해치지 않고 복사를 할 수 있었기에, 이전처럼 비정상적으로 클릭 기능이 작동하지 않았다. Vuex의 mutations를 통해서 state를 깔끔하게 변경해 주었다. Mutations는 Actions에서 실행시키는데 이렇게 컴포넌트 안에서 this.$store.commit 으로 바로 실행할 수 있다. 기존의 방법대로 단순히 map만으로 state를 변경한다면 예상치 못한 에러가 발생할 수 있다고 한다.

문제해결 완료!

profile
빵굽는 프론트엔드 개발자

0개의 댓글