[VueJS] VueJS - Vuejs Template을 컴포넌트화하는 방법

Vuejs Template을 컴포넌트화하는 방법

Vuejs 내에서도 각 template을 컴포넌트 하는 방법에는 여러가지 방법이 존재한다. 프로젝트를 진행하면서 어떠한 방법이 좀더 효율적일지를 찾아보다 한 블로그에서 컴포넌트화하는 방법에 대한 글이 있어 그 글에 대해 번역을 하며 개인적인 생각을 덧붙였다. 원글이 궁금하면 하단에 출처를 기재해두었으니, 참고하면 된다.

원글에서는 총 7가지에 대해서 설명을 하고 있다. 물론 대부분의 내용은 공식홈페이지에서도 나오는 방법이며, 이 중 6가지 방법에 대해 기술한다.

  • String
  • Template literal
  • X-Templates
  • Inline
  • Render functions
  • Single page components

String

가장 기본적인 방법으로 template에 대한 것을 JS 파일에 String 형태로 정해놓는 것이다. 광범위한 브라우저의 호환성이라는 장점이 있지만, 이러한 방법은 간단한 형태의 dom 형태를 가지는 경우에는 간단하게 사용할 수 있을지 모르지만 사실 실제 프로젝트에서 사용하기 에는 가독성, 확장성을 봤을 때는 효율적인 코드를 만들 수 없어보인다.

Vue.component('tab-example', {
  template: '<div class="module_tab" id="tab" data-module-tab="wrap"><ul><li v-for="(item, index) in items" :class="[{ on : index == activeIndex }]"><button @click="tabToggle( index )" type="button"><span></span></button><div class="module_tab__contents"></div></li></ul></div>',
  data () {
    return {
      activeIndex: 0,
      items: [{
        'button': 'tab01',
        'contents': 'tab01 contents'
      },{
        'button': 'tab02',
        'contents': 'tab02 contents'
      },{
        'button': 'tab03',
        'contents': 'tab03 contents'
      }]
    }
  },
  methods: {
    tabToggle (targetIndex) {
      this.activeIndex = targetIndex;
    }
  }
});

예제보러가기

Template literal

위의 방식에서 가독성에 대한 부분을 해결하기 위한 방식으로 literal방식으로 template을 선언함으로써 String 형태보다 조금 더 가독성이 향상되었다. 하지만 이 부분 역시 IDE에서 Vue라는 언어를 지원해주는 것이 아닌 그냥 JS 파일로만 인식을 해, 구문 강조 라던지 탭, 개행 등의 문제가 생길 수 있다. 뿐만 아니라 이 또한 직관성이 떨어진다는 단점이 있다.

Vue.component('tab-example', {
  template: `<div class="module_tab" id="tab" data-module-tab="wrap">
              <ul>
                <li v-for="(item, index) in items" :class="[{ on : index == activeIndex }]">
                  <button @click="tabToggle( index )" type="button"><span></span></button>
                  <div class="module_tab__contents"></div>
                </li>
              </ul>
            </div>`,
  data () {
    return {
      activeIndex: 0,
      items: [{
        'button': 'tab01',
        'contents': 'tab01 contents'
      },{
        'button': 'tab02',
        'contents': 'tab02 contents'
      },{
        'button': 'tab03',
        'contents': 'tab03 contents'
      }]
    }
  },
  methods: {
    tabToggle (targetIndex) {
      this.activeIndex = targetIndex;
    }
  }
});

예제보러가기

X-Template

위의 literal 방법에서 조금 더 향상된 방법으로 Template의 전체도입이 아닌 경우에는 이 방법을 많이 사용하는 것 같다. 한때 handlebars template을 이용하여 간단한 이벤트성 페이지에 데이터를 바인딩할 때 역시 이와 같은 방법으로 사용을 했었다. script의 type이 text/x-template 일 경우 해당 Dom 구조를 참조하여 컴포넌트화 시킨다. 다만 이 부분에 대한 단점으로는 template의 구조와 나머지 구성 요소가 분리된다는 것이다.

<div id="tab">
  <tab-example></tab-example>
</div>
<script type="text/x-template" id="tab-template">
  <div class="module_tab" id="tab" data-module-tab="wrap">
      <ul>
        <li v-for="(item, index) in items" :class="[{ on : index == activeIndex }]">
          <button @click="tabToggle( index )" type="button"><span></span></button>
          <div class="module_tab__contents"></div>
        </li>
      </ul>
    </div>
</script>
Vue.component('tab-example', {
  template: '#tab-template',
  data () {
    return {
      activeIndex: 0,
      items: [{
        'button': 'tab01',
        'contents': 'tab01 contents'
      },{
        'button': 'tab02',
        'contents': 'tab02 contents'
      },{
        'button': 'tab03',
        'contents': 'tab03 contents'
      }]
    }
  },
  methods: {
    tabToggle (targetIndex) {
      this.activeIndex = targetIndex;
    }
  }
});

예제보러가기

Inline-template

이 방식은 위의 x-template 방식과는 크게 차이가 없지만, 여기에서는 inline-template 이라는 특수한 attribute를 사용했다. 하위에 이 attribute를 가지고 있을 경우, 컴포넌트는 그 내용을 템플릿으로 취급하여 사용하게 된다. 하지만 이러한 방법은 그 컴포넌트의 대상이 어디까지인지 추론하기가 힘들다는 단점이 있다.

<div id="tab">
  <tab-example inline-template>
    <div class="module_tab" id="tab" data-module-tab="wrap">
      <ul>
        <li v-for="(item, index) in items" :class="[{ on : index == activeIndex }]">
          <button @click="tabToggle( index )" type="button"><span></span></button>
          <div class="module_tab__contents"></div>
        </li>
      </ul>
    </div>
  </tab-example>
</div>
Vue.component('tab-example', {
  data () {
    return {
      activeIndex: 0,
      items: [{
        'button': 'tab01',
        'contents': 'tab01 contents'
      },{
        'button': 'tab02',
        'contents': 'tab02 contents'
      },{
        'button': 'tab03',
        'contents': 'tab03 contents'
      }]
    }
  },
  methods: {
    tabToggle (targetIndex) {
      this.activeIndex = targetIndex;
    }
  }
});
new Vue({
  el: '#tab'
})

예제보러가기

Rendering 함수와 JSX

Render 함수를 이용하는 경우에는 템플릿을 객체 형태로 정의해야 한다. Vuejs는 템플릿을 지향하기 때문에 render 함수를 이용해서 사용하는 것은 vue에는 맞지 않다. 오히려 공식 홈페이지에서는 HTML에 작성할 것을 권장한다. 물론 개선을 한다고 하면 개선이 가능하겠지만, 일단 기본적으로 Dom tree 파악에 대한 가독성도 좋지 않을 뿐 아니라 구조가 복잡할 경우에는 Array와 Object의 형식을 지키면 만든다는 것이 쉽지 않다.

Vue.component('tab-example', {
      data () {
        return {
          activeIndex: 0,
          items: [{
            'button': 'tab01',
            'contents': 'tab01 contents'
          },{
            'button': 'tab02',
            'contents': 'tab02 contents'
          },{
            'button': 'tab03',
            'contents': 'tab03 contents'
          }]
        }
      },
      render (createElement) {
        return createElement(
          'div', {
            attrs: {
              'class': 'module_tab',
              'id': 'tab',
              'data-module-tab': 'wrap'
            }
          },[
            createElement(
              'ul', [
                createElement(
                  'li', {
                    attrs: {
                      'class': 'on'
                    }
                  }, [
                    createElement(
                      'button', {
                        attrs: {
                          'type': 'button'
                        }
                      }, [
                        createElement(
                          'span', [this.items[0]['button']]
                        )
                      ]
                    ),
                    createElement(
                      'div', {
                        attrs: {
                          'class': 'module_tab__contents'
                        }
                      }, [this.items[0]['contents']]
                    )
                  ]
                ),
                createElement(
                  'li', [
                    createElement(
                      'button', {
                        attrs: {
                          'type': 'button'
                        }
                      }, [
                        createElement(
                          'span', [this.items[1]['button']]
                        )
                      ]
                    ),
                    createElement(
                      'div', {
                        attrs: {
                          'class': 'module_tab__contents'
                        }
                      }, [this.items[1]['contents']]
                    )
                  ]
                ),
                createElement(
                  'li', [
                    createElement(
                      'button', {
                        attrs: {
                          'type': 'button'
                        }
                      }, [
                        createElement(
                          'span', [this.items[2]['button']]
                        )
                      ]
                    ),
                    createElement(
                      'div', {
                        attrs: {
                          'class': 'module_tab__contents'
                        }
                      }, [this.items[2]['contents']]
                    )
                  ]
                )
              ]
            )
          ]
        )
      }
    });

예제보러가기

Single page components

Vue에서 권고하는 방법이자, 빌드 환경에 익숙하다면 제일 Vue를 Vue 답게 사용할 수 있는 방법이 아닐까 싶다. 물론 이러한 방법의 경우 환경적인 제약 ( 프로젝트의 부분적 도입 등)이 다소 있을 수 있으나, 하나의 어플리케이션을 구축한다고 하면 제일 최상의 방법이 아닐까 라는 생각이 든다. 마지막 방식에 대한 것은 실제 빌드 환경에서만 확인이 가능하기에 따로 예제 파일을 링크에 담아 두지 않는다. 단점으로는 일부 IDE에서는 구문 강조 등의 지원 여부가 미흡하지만 이 부분 역시 IDE에 따라 각자의 패키지가 있으니 그러한 패키지의 도움을 받으면 어느정도 해결할 수 있는 부분이라고 생각한다.

<template>
<div id="tab">
  <tab-example inline-template>
    <div class="module_tab" id="tab" data-module-tab="wrap">
      <ul>
        <li v-for="(item, index) in items" :class="[{ on : index == activeIndex }]">
          <button @click="tabToggle( index )" type="button"><span></span></button>
          <div class="module_tab__contents"></div>
        </li>
      </ul>
    </div>
  </tab-example>
</div>
</template>
export default {
  name: 'TabModule',
  data () {
    return {
      activeIndex: 0,
      items: [{
        'button': 'tab01',
        'contents': 'tab01 contents'
      },{
        'button': 'tab02',
        'contents': 'tab02 contents'
      },{
        'button': 'tab03',
        'contents': 'tab03 contents'
      }]
    }
  },
  methods: {
    tabToggle (targetIndex) {
      this.activeIndex = targetIndex;
    }
  }
}
<script>
</script>
<style lang="scss">
  body,html ul,li, button { margin:0; padding:0; }
  ul,li{ list-style:none; }
  h1{ padding:0; margin:0 0 20px 0; font-size:30px; line-height:32px; }
  .module_tab{ width:450px; height:300px; }
  .module_tab ul{ position:relative; }
  .module_tab ul:after{ display:block; clear:both; content:""; }
  .module_tab ul li { float:left;  }
  .module_tab ul li.last{ margin-left:-1px; }
  .module_tab ul li button{ display:block; width:150px; height:48px; border:1px solid #999; text-align:center; background-color:#ececec; color:#999; }
  .module_tab ul li button span{ position:relative; }
  .module_tab ul li.on button { background-color:#fff; color:#000;  }
  .module_tab ul li.on button span{ padding-bottom:5px; border-bottom:2px solid #999; }
  .module_tab ul li .module_tab__contents{ display:none; position:absolute; left:-1px; top:47px; height:148px; padding-top:100px; border:1px solid #999; font-size:30px; line-height:32px; text-align:center; }
  .module_tab ul li .module_tab__contents{ width:448px; }
  .module_tab ul li.on .module_tab__contents{ display:block; }
</style>

이와 같이 Vuejs를 component화하는 방법에는 다양한 방법이 있다. 위에서 설명하지 못한 jsx를 이용한 방법도 있으니, 필요한 경우 다른 블로그를 참조하면 좋을 것 같다. 위의 방법 외에도 또 다른 방법이 있을 수 있으니 이러한 부분은 프로젝트의 성격 및 업무 환경에 맞춰 사용하면 될 것 같다.


출처

Comments