본문 바로가기

FrontEnd

Vue3 컴포넌트 디자인 패턴 : Object binding

반응형

v-on, v-bind를 이용해 바인딩을 간소화하는 방법을 알아봅니다.

Vue3

이전 시리즈

2022.12.21 - [Vue.js] - Vue3 컴포넌트 디자인 패턴 소개

Vue3 컴포넌트 디자인 패턴 소개

vue mastery의 컴포넌트 디자인 패턴(component-design-patterns) 강의를 학습하고 정리한 내용입니다. 디자인 패턴에 관하여 어제 한 후배한테 실무에서 디자인 패턴을 사용하냐는 질문을 들었는데, 리액

itchallenger.tistory.com

2022.12.22 - [Vue.js] - Vue3 컴포넌트 디자인 패턴 : Props

Vue3 컴포넌트 디자인 패턴 : Props

컴포넌트 디자인 패턴은 컴포넌트를 개발하면서 마주치게 되는 특정 문제를 해결하면서 소프트웨어의 유지보수성 / 재사용성을 높이는 개발을 위한 도구다. 이전 시리즈 링크 : 2022.12.21 - [분류

itchallenger.tistory.com

2022.12.22 - [Vue.js] - Vue3 컴포넌트 디자인 패턴 : Slots

Vue3 컴포넌트 디자인 패턴 : Slots

Vue3에서 slot을 사용하여 유연한 구조, 유연한 합성이 가능한 컴포넌트를 개발하는 방법을 알아봅니다. 이전 시리즈 2022.12.21 - [Vue.js] - Vue3 컴포넌트 디자인 패턴 소개 2022.12.22 - [분류 전체보기] -

itchallenger.tistory.com


2022.12.22 - [Vue.js] - Vue3 컴포넌트 디자인 패턴 : Scoped Slots

Vue3 컴포넌트 디자인 패턴 : Scoped Slots

Scoped Slot을 이용해 하위 컴포넌트의 데이터에 접근하는 방법을 알아봅니다. 이전 시리즈 2022.12.21 - [Vue.js] - Vue3 컴포넌트 디자인 패턴 소개 Vue3 컴포넌트 디자인 패턴 소개 vue mastery의 컴포넌트

itchallenger.tistory.com

모든 것을 지배하는 하나의 객체

Vue 개발자가 배우는 첫 번째 사항 중 하나는 v-bind 및 v-on을 사용하여 동적 바인딩 및 이벤트 리스너로 HTML을 강화하는 방법입니다. v-bind 및 v-on의 선언적 특성으로 인해 다른 개발자가 매우 쉽게 따라할 수 있습니다.
 
<input 
  :id="inputId" 
  :data-tooltip="tooltipText"
  @change="updateForm" 
  @mouseover="showTooltip"
/>
 
하지만 컴포넌트가 커지고 복잡해짐에 따라 이 접근 방식은 제한적이 되고 혼란을 야기할 수도 있습니다.
우리는 v-bind 및 v-on에 대해 여러 값을 동적으로 정의할 수 있습니다.
 

인수 없이 v-bind

앞서 언급했듯이 대부분의 Vue 개발자는 다음 구문에 익숙합니다.
<template>
  <img v-bind:src="imageAttrs.source" v-bind:alt="imageAttrs.text" />
</template>
<script>
export default {
  data() {
    return {
      imageAttrs: {
        src: '/vue-mastery-logo.png',
        text: 'Vue Mastery Logo'
      }
    }
  }
}
</script>

이 구문은 사실 v-bind 객체에 인수를 전달함으로써 특정 속성을 이 특정 속성에 바인딩하고 싶다고 Vue에 알리는 것입니다.
그러나 더 당혹스러운 것은 이 인수가 옵셔널하다는 것입니다.
믿거나 말거나 우리 코드는 실제로 다음과 같이 작성할 수 있습니다.

<img v-bind="{ src: imageAttrs.source, alt: imageAttrs.text }" />
이것은 다음과 같이 한 단계 더 추상화할 수 있습니다.
<img v-bind="imageAttrs" />
이것은 매우 짧고 간결하지만 명료하고 사용하기 쉽습니다.
하지만 대부분의 사람들은 속성을 선언하는 원래 방법을 여전히 선호합니다.

인수 없이 v-on

v-on과 관련하여 대부분의 사람들은 다음 사용법에 익숙합니다.
<template>
  <img v-on:click="openGallery" v-on:mouseover="showTooltip" />
</template>
<script>
export default {
  methods: {
    openGallery() { ... },
    showTooltip() { ... }
  }
}
</script>

v-bind와 마찬가지로 우리가 정의한 이벤트 이름은 실제로 v-on 객체에 전달되는 인수입니다.
결과적으로 img 요소를 다음과 같이 다시 작성할 수도 있습니다.

<template>
  <img v-on="{ click: openGallery, mouseover: showTooltip }" />
</template>
이것은 다음과 같이 한 단계 더 추상화할 수 있습니다.
<template>
  <img v-on="inputEvents" />
</template>
<script>
export default {
  computed: {
    inputEvents: {
      click: this.openGallery,
      mouseover: this.showTooltip
    }
  },
  methods: {
    openGallery() { ... },
    showTooltip() { ... }
  }
}
</script>

그러나 앞서 v-bind 섹션에서 언급한 것처럼 우리 대부분은 이해하기 쉽고 필요한 경우 변경하기 쉽기 때문에
여전히 원래 구문을 선호합니다.


언제 이것이 유용한가요?

이전 예제에서 설명한 것처럼 구조를 이해하기 쉬운 간단한 컴포넌트는 추상화된 구문을 사용할 이유가 거의 없습니다.
그러나 컴포넌트가 점점 더 복잡해지고 이해하기 어려워지면 해당 기술이 필요합니다.

NewsFeed Scenario

API가 편집자가 원하는 것에 따라 변경되는 일련의 컴포넌트를 전달하는 NewsFeed 컴포넌트를 구축하는 시나리오를 생각해 봅시다.
즉, 때때로 NewsArticle 컴포넌트를 가져오지만 때로는 NewsAd 컴포넌트를 가져옵니다.
 
이를 구현할 때 다음과 같이 시작할 수 있습니다.
<template>
  <main>
    <Component 
      v-for="content in apiResponse"
      :key="content.id"
      :is="content.type"
      :article-title="content.title"
      :article-content="content.body"
      :ad-image="content.image"
      :ad-heading="content.heading"
      @click="content.type === 'NewsArticle' ? openArticle : openAd"
      @mouseover="content.type === 'NewsArticle' ? showPreview : trackAdEvent"
    />
  </main>
</template>​

  • Component 컴포넌트를 사용, content.type에 의해 결정되는 is 속성을 사용해 NewsArticle 또는 NewsAd 컴포넌트를 동적으로 렌더링합니다.
  • NewsArticle 컴포넌트 바인딩 동적 속성: atricle-title, article-content
  • NewsAd 컴포넌트 바인딩 동적 속성: ad-image, ad-heading

특정 prop과 이벤트는 특정 컴포넌트에만 중요하다는 것이 분명합니다.
속성 및 이벤트 목록이 길어지면 컴포넌트이해 및 관리가 점점 어려워 질 것이 분명합니다.

Refactoring NewsFeed

컴포넌트 이해 및 관리가 어려워지면 새로운 기술과 추상화를 찾아야 할 때입니다.
배운 기술을 사용하여 NewsFeed 컴포넌트를 리팩터링해 보겠습니다.

<template>
  <main>
    <Component 
      v-for="content in apiResponse"
      :key="content.id"
      :is="content.type"
      v-bind="feedItem(content).attrs"
      v-on="feedItem(content).events"
    />
  </main>
</template>
<script>
export default {
  methods: {
    feedItem(item) {
      if (item.type === 'NewsArticle') {
        return {
          attrs: {
            'article-title': item.title,
            'article-content': item.content
          },
          events: {
            click: this.openArticle,
            mouseover: this.showPreview
          }
        }
      } else if (item.type === 'NewsAd') {
        return {
          attrs: {
            'ad-image': item.image,
            'ad-heading': item.heading
          },
          events: {
            click: this.openAd,
            mouseover: this.trackAdEvent
          }
        }
      }
    }
  }
}
</script>

이를 통해 단일 HTML 엘리먼트에 여러 컴포넌트의 관심사를 섞지 않으면서,
특정 컴포넌트에 대한 책임을 적절하게 위임했습니다.

마치며

코드 리팩터링의 주요 지표는 가독성과 관리용이성 입니다.
객체 구문으로 추상화 하기 전에 이렇게 하면 코드 읽기가 쉬울지, 관리하기 쉬울지(관심사의 분리)를 생각해보세요

반응형