계발하는 개발자

[Vue.js] 컴포넌트 provide(제공) / inject(주입) 이해하기 본문

💻 Frontend/Vue.js

[Vue.js] 컴포넌트 provide(제공) / inject(주입) 이해하기

dev_genie 2023. 8. 17. 02:49

어제 포스팅했던 mixins 옵션을 알기 전에, 부모 컴포넌트에서 자식 컴포넌트로 메서드 전달을 위해 기존 사용하였던

provide/inject 에 대해 정리해볼까 한다.

먼저 이와 비슷한 기능을 하는 props가 아닌 provide/inject를 고려했던 이유는 자식컴포넌트의 경우 부모컴포넌트에서 사용하는 단순 메서드가 필요한 것이지, 부모측 데이터는 필요없어서였다.

이런 경우 props로 전달받기엔 많은 번거로움이 있다고 생각했다.

이 포스팅은 일전 사용하였던 경험을 바탕으로 정리한 것이며, 지금은 해당 코드 블럭을 mixins으로 대체해서 코드상에서는 제외되었다.

 

Provide/inject 란?

  • 목적
    • 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하거나, 부모-자식 계층 구조를 따르지 않는 컴포넌트 간에 데이터를 공유하는 데 사용된다.
    • 주로 복잡한 컴포넌트 구조에서 다양한 컴포넌트 간에 데이터를 공유하거나 전역 상태 관리와 유사한 기능을 구현할 때 사용된다.
  • 세부 옵션 정의
    • provide : 부모 컴포넌트가 데이터 제공을 위해 사용하는 옵션
    • inject - 자식 컴포넌트가 데이터 사용을 위해 사용하는 옵션
  • 특징
    • provide 하는 데이터는 반응성을 가지지 않는다.
      • 이 말인 즉슨, provide한 데이터가 변경되어도 inject한 데이터는 변경되지 않는다.
      • 이를 반응형으로 지원하기 위해선 ref/reactive나 computed를 이용해야 한다.
    • 부모에서 자식으로 데이터 전달을 할 때 유용하지만 컴포넌트간 의존성 증가, 데이터 복잡성 증가라는 단점을 가진다.
      • vue 공식문서에서는 아래와 같이 일반 어플리케이션 코드에서는 사용하지 않는 것이 좋다 한다.
`provide`와 `inject`는 주로 고급 플러그인/컴포넌트 라이브러리를 위해 제공됩니다. 일반 애플리케이션 코드에서는 사용하지 않는 것이 좋습니다.

 

[부모컴포넌트 : goods-comp]

// [2] 뷰컴포넌트 서브페이지 상품
Vue.component("goods-comp", {
  template: `
    <section>
      <div class="container">
      <div class="pagewrap" v-on="initSetSubSrc()">
          <!-- 상품리스트 박스 -->
          <div class="prdbx">
            <div class="prdwrap">
                <!-- 상품리스트 -->
                  <ul class="ui-col4">
                    <template v-for="(v,i) in prdData[dataNum()]">
                        <template v-for="(a,b) in prdData[dataNum()][i]" v-if="b === $store.state.curUrl2 || $store.state.curUrl2 === '전체'">
                            <li v-for="(x,y) in a" :key="y" v-on:mouseover="handleMouseOver" v-on:mouseleave="handleMouseLeave" v-on:click="getData(prdData[dataNum()][i], y)">
                                <div class="ui-prod-bx">
                                    <a href="#">
                                        <div class="prod-detail-img">
                                            <img v-bind:src="'./images/goods/' + $store.state.curUrl0 + '/' + i + '/' + x.img + '.jpg'" alt="상품이미지">
                                        </div>
                                    </a>
                                    <div title="찜하기" class="product_like" v-on:click="addWish(prdData[dataNum()][i],y,1)">
                                        <button type="button" class="fa-solid fa-heart"></button>
                                    </div>
                                </div>
                                <div class="item-detail">
                                    <div class="prod-txt">
                                        <strong class="brand">슈펜</strong>
                                        <p>{{x.name}}</p>
                                    </div>
                                    <span class="original-price">
                                        <em>{{numberWithCommas(x.oprice)}}</em>
                                        <span v-if="x.oprice">원</span>
                                    </span>
                                    <br>
                                    <span class="discount-price">
                                        <em>{{numberWithCommas(x.dprice)}}</em>
                                        <span>원</span>
                                    </span>
                                    <span class="percent-price" v-if="x.oprice && x.dprice">
                                        <em>{{calculateDiscount(x.oprice,x.dprice)}}</em>
                                    </span>
                                    <div class="box_grade">
                                        <div class="star">
                                            <span v-if="x.review">{{'(' + x.review + ')'}}</span>
                                        </div>
                                    </div>
                                </div>
                            </li>
                        </template>
                    </template>
                  </ul>
              </div>
          </div>
      	</div>
      </div>
      <!-- 여기부터 디테일페이지! -->
      <dt-comp></dt-comp>
    </section>
  `,

위 코드는 기존 삭제 전 코드이고, 내용이 많기 때문에 일정 부분은 잘라내었다.

문제가 되었던 부분은 부모인 goods-comp의 [numberWithCommas, calculateDiscount, minusBtn, plusBtn] 이라는 이름의 메서드들을 자식인 dt-comp에서도 호출되도록 하는 것이었다.

아무리 부모 컴포넌트 안에 한데 묶여있더라도 부모 컴포넌트의 메서드를 자식 컴포넌트에 전달해주지 않고서는 호출이 불가했다.

  methods: {
  // 정규식함수(숫자 세자리마다 콤마해주는 기능)
    numberWithCommas: function (x) {
      return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    },
    // 정가/할인가 비교해서 할인율 계산
    calculateDiscount: function (oprice, dprice) {
      // 정가와 할인가 중 하나라도 빈값이면 빈값 반환
      if (!oprice || !dprice) {
        return "";
      }
      const discount = ((oprice - dprice) / oprice) * 100;
      return Math.floor(discount) + "%";
    },
    // 더하기함수
    plusBtn: function () {
      let num = $(".opt_num input").val();
      num++;
      // 업데이트
      $(".opt_num input").val(num);
      store.state.result = num;
    },
    // 빼기함수
    minusBtn: function () {
      let num = $(".opt_num input").val();
      num--;
      if (num === 0) return;
      // 업데이트
      $(".opt_num input").val(num);
      store.state.result = num;
    },
  }

그래서 방법을 알아보던 중 알게된 것이 바로 provide/inject 였다.

(사실 이와 관련한 방법으로 그 유명한 props도 있는데 이건 또 차차 정리해보려한다.)

 

본격적인 provide/inject 사용을 위해 부모컴포넌트 provide 옵션에 정의된 함수를 자식컴포넌트로 내보낸다.

 <!-- 여기부터 디테일페이지! -->
  <dt-comp></dt-comp>
</section>
`,
provide: function () {
    return {
      // dt-comp에 전달할 메서드들
      numberWithCommas: this.numberWithCommas,
      calculateDiscount: this.calculateDiscount,
      plusBtn: this.plusBtn,
      minusBtn: this.minusBtn,
    };
},

내보냈으니 이제 받아줘야겠지?

해당 메서드를 필요로하는 자식컴포넌트 등록한 구역으로 가서 inject 한다.

말그대로 provide - inject 주거니 받거니 하는 관계가 export - import 랑도 비슷해보인다.

 

[자식컴포넌트 : dt-comp]

// [5] 뷰컴포넌트 - 상품디테일
Vue.component("dt-comp", {
  inject: ["numberWithCommas", "calculateDiscount", "plusBtn", "minusBtn"],
  template: dtData.dtComp,
  mounted() {
    // 부모 컴포넌트에게서 전달받은 메서드 호출!
    if (typeof this.numberWithCommas && this.calculateDiscount && this.plusBtn && this.minusBtn === "function") {
      this.numberWithCommas();
      this.calculateDiscount();
      this.plusBtn();
      this.minusBtn();
}

그리고 자식 컴포넌트가 DOM에 마운트될때 해당 함수를 실행하도록 한다.

그런 후에 렌더링될 dt-comp 템플릿으로 가서 특정한 액션이 있는 경우나 인자값을 넘겨줘야 하는 경우에 인자값을 전달해서 호출해준다.

 <div class="price">
    <div class="txt-def">
        <em>
            {{numberWithCommas($store.state.dtoprice)}}
            <span v-if="$store.state.dtoprice">원</span>
        </em>
    </div>
    <div class="txt-dsc">
        <em>{{numberWithCommas($store.state.dtdprice)}}</em>
        <span>원</span>
        <span class="txt-percent">
            <em>{{calculateDiscount($store.state.dtoprice, $store.state.dtdprice)}}</em>
        </span>
    </div>
</div>
<div class="opt_num">
    <a href="#" role="button" class="minus" v-on:click.prevent="minusBtn()">수량감소</a>
    <a href="#" role="button" class="plus" v-on:click.prevent="plusBtn()">수량증가</a>
    <label>
        <input type="number" class="num" title="수량" value="1">
    </label>
</div>

 

그러면 아래같이 에러없이 각 메서드 기능들이 잘 작동한다!

(구동이 잘 되는 부분에 형광펜 처리하였다.)

numberWithCommas, calculateDiscount 함수 실행된 모습
plusBtn, minusBtn 함수 실행된 모습

위의 방법으로 부모컴포넌트로부터 메서드들을 전달받아 자식 컴포넌트에서도 동일한 함수를 실행시켰다.

가격에 세자리 콤마, 원가와 할인가를 비교하여 구한 할인률 표시, 수량조절 및 수량에 따른 총 합계가 계산되어 표시된다.

 


* 이해에 도움이 됐던 자료 및 출처 :

 

Provide(제공) / Inject(주입) | Vue.js

 

ko.vuejs.org

 

[VueJS] provide, inject 사용하는 방법 알아보기

VueJS에서 컴포넌트 사이의 데이터를 전달하는 방법중 하나인 Provide / Inject 방법에 대하여 알아봅니다.

webisfree.com

 

LIST
profile

dev_genie

@dev_genie

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!