[Vue] 컴포넌트

vue.js에 대한 이해 - 3

Posted by owin2828 on 2020-10-26 16:06 · 10 mins read

들어가기 앞서

이전 포스팅에 이어 뷰의 컴포넌트의 정의, 유효범위 및 사용법에 대해 알아보도록 한다.

1. 컴포넌트란?


컴포넌트(Component)란 조합하여 화면을 구성할 수 있는 블록을 의미하며 다음과 같은 장점을 지닌다.

  1. 구조화하여 일괄적인 패턴으로 개발 가능
  2. 코드 재사용성이 높아짐
  3. 남의 코드를 직관적으로 이해하기 편함

컴포넌트로 구분한 화면 영역 간의 관계도


2. 전역 컴포넌트 VS 지역 컴포넌트


컴포넌트를 등록할 때, 전역지역 두 가지 방법으로 가능하다.
지역(Local) 컴포넌트는 특정 인스턴스에서만 유효한 범위를, 전역(Global) 컴포넌트는 여러 인스턴스에서 공통으로 사용할 수 있다.

2-1. 전역 컴포넌트

전역 컴포넌트는 다음과 같이 Vue 변수를 이용하여 등록하며, .component() 함수를 호출하여 실행한다.

<html>
  <head>
    <title>Vue Component Registration</title>
  </head>
  <body>
    <div id="app">
      <button>컴포넌트 등록</button>
      <my-component></my-component>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
    <script>
      Vue.component('my-component', {
        template: '<div>전역 컴포넌트가 등록되었습니다!</div>'
      });

      new Vue({
        el: '#app'
      });
    </script>
  </body>
</html>

위의 my-component 태그부분은 최종적으로 다음과 같이 변경된다.

    <div id="app">
      <button>컴포넌트 등록</button>
      <!-- 치환된 모습 -->
      <div>전역 컴포넌트가 등록되었습니다!</div>
    </div>


2-2. 지역 컴포넌트

지역 컴포넌트는 인스턴스에 component 속성을 추가하여 이름과 내용을 정의한다.

  ...
    <script>
      var cmp = {
        // 컴포넌트 내용
        template: '<div>지역 컴포넌트가 등록되었습니다!</div>'
      };

      new Vue({
        el: '#app',
        components: {
          'my-local-component': cmp
        }
      });
    </script>


2-3. 전역 vs 지역 컴포넌트 차이

<html>
  <head>
    <title>Vue Local and Global Components</title>
  </head>
  <body>
    <div id="app">
      <h3>첫 번째 인스턴스 영역</h3>
      <my-global-component></my-global-component>
      <my-local-component></my-local-component>
    </div>
    <hr>
    <div id="app2">
      <h3>두 번째 인스턴스 영역</h3>
      <my-global-component></my-global-component>
      <my-local-component></my-local-component>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
    <script>
      // 전역 컴포넌트 등록
      Vue.component('my-global-component', {
        template: '<div>전역 컴포넌트 입니다.</div>'
      });

      // 지역 컴포넌트 내용
      var cmp = {
        template: '<div>지역 컴포넌트 입니다.</div>'
      };

      new Vue({
        el: '#app',
        // 지역 컴포넌트 등록
        components: {
          'my-local-component': cmp
        }
      });

      // 두 번째 인스턴스
      new Vue({
        el: '#app2'
      });
    </script>
  </body>
</html>

위와 같은 코드에서 첫번째 인스턴스 영역에서는 전역, 지역이 모두 표기되나 두번째 인스턴스에서는 전역만 노출된다.
전역 컴포넌트는 인스턴스를 새로 생성할 때마다, 인스턴스에 components 속성으로 등록할 필요 없이 사용 가능하나,
지역 컴포넌트는 새 인스턴스를 사용할 때마다 등록해줘야 한다.

3. 컴포넌트 통신


뷰 컴포넌트는 자체적으로 고유한 유효 범위(Scope)을 지니기 때문에 같은 웹 페이지에 존재하더라도, 데이터 공유가 불가능하다.
따라서 뷰 프레임워크 자체에서 정의한 컴포넌트 데이터 전달 방법을 따라야 하는데, 가장 기본적인 방법은 상위-하위 컴포넌트 간의 데이터전달이다.
기본적으로 상위 -> 하위로는 props라는 특별한 속성을 전달하고, 하위 -> 상위로는 이벤트만 전달이 가능하다.

3-1. 상위 -> 하위 데이터 전달

상위 -> 하위 컴포넌트로 데이터를 전달할 때는 props라는 속성을 다음과 같은 순서로 사용한다.

  1. 하위 컴포넌트 속성에 props를 정의
  2. 상위 컴포넌트의 해당하는 태그에 v-bind 속성을 추가

이를 코드로 나타내면 다음과 같다.

<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue Props Sample</title>
  </head>
  <body>
    <div id="app">
      <!-- 팁 : 오른쪽에서 왼쪽으로 속성을 읽으면 더 수월합니다. -->
      <child-component v-bind:propsdata="message"></child-component>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
    <script>
      Vue.component('child-component', {
        props: ['propsdata'],
        template: '<p>{ { propsdata }}</p>',
      });

      new Vue({
        el: '#app',
        data: {
          message: 'Hello Vue! passed from Parent Component'
        }
      });
    </script>
  </body>
</html>


3-2. 하위 -> 상위 이벤트 전달

하위 -> 상위 컴포넌트의 통신은 이벤트(Event)를 발생시켜 신호를 보내는 방식으로 작동한다.
이를 처리하기 위해 상위 컴포넌트는 이벤트를 기다리고 있다가 하위 컴포넌트에서 특정 이벤트가 발생하면,
상위 컴포넌트에서 해당 이벤트를 수신하여 상위 컴포넌트의 메서드를 호출한다.

뷰 공식 사이트에서 이벤트 발생 사용방법에서는 하위 -> 상위 컴포넌트로 데이터의 전달은 다루지 않는다고 한다.
뷰의 단방향 데이터 흐름에 어긋나는 방법이기 때문인데, 복잡한 어플리케이션에서는 전달해야할 경우가 생기게 된다.
이를 위한 방법이 이벤트 버스(Event Bus)이다.

이벤트 발생과 수신은 $emit()v-on: 속성을 사용하여 구현하며, 이를 코드로 나타내면 다음과 같다.

<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue Event Emit Sample</title>
  </head>
  <body>
    <div id="app">
      <child-component v-on:show-log="printText"></child-component>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
    <script>
      Vue.component('child-component', {
        template: '<button v-on:click="showLog">show</button>',
        methods: {
          showLog: function() {
            this.$emit('show-log');
          }
        }
      });

      new Vue({
        el: '#app',
        data: {
          message: 'Hello Vue! passed from Parent Component'
        },
        methods: {
          printText: function() {
            console.log("received an event");
          }
        }
      });
    </script>
  </body>
</html>

$emit()을 호출하면 괄호안에 정의된 이벤트가 발생되며, 해당 이벤트 명을 v-on:속성에지정하고 발생시킬 메서드를 지정한다.

3-3. 같은 레벨의 컴포넌트 간 통신

기본적인 통신 규칙을 지키며 동레벨의 컴포넌트에게 데이터를 전달하기 위해서는, 해당 컴포넌트들의 공통 상위 컴포넌트를 통해 전달해야 한다.
즉, 하위 컴포넌트1 -> 공통 상위 컴포넌트 -> 하위 컴포넌트2의 방식으로 전달되야 하는데, 이를 해결하기 위한 방법이 이벤트 버스이다.

<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue Event Bus Sample</title>
  </head>
  <body>
    <div id="app">
      <child-component></child-component>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
    <script>
      var eventBus = new Vue();

      Vue.component('child-component', {
        template: '<div>하위 컴포넌트 영역입니다.<button v-on:click="showLog">show</button></div>',
        methods: {
          showLog: function() {
            eventBus.$emit('triggerEventBus', 100);
          }
        }
      });

      var app = new Vue({
        el: '#app',
        created: function() {
          eventBus.$on('triggerEventBus', function(value){
            console.log("이벤트를 전달 받음. 전달 받은 값 : ", value);
          });
        }
      });
    </script>
  </body>
</html>

위 코드는 show 버튼을 클릭했을 때, 이벤트 버스를 이용하여 상위 컴포넌트로 데이터를 전달하는 코드이며 다음과 같은 흐름으로 진행된다.

  1. show 버튼 클릭
  2. showLog() 실행
  3. eventBus의 이벤트 발생
  4. 상위 컴포넌트 created()에 있는 eventBus.$on()에서 전달 받음
  5. 콘솔에 전달된 인자 값 100이 출력

위와 같이 이벤트 버스를 활용하면 props 속성을 이용하지 않고 원하는 컴포넌트 간의 데이터 교환이 가능하다.
하지만 컴포넌트가 많아지면, 어디서 어디로 보내는지 관리가 되지 않는 문제점이 발생한다.
이 문제를 해결하기 위해서는 Vuex라는 상태 관리 도구가 필요하다.


끝마치며

여태까지 뷰의 기본적인 개요 및 인스턴스와 컴포넌트에 대하여 알아보았다.
다음 포스팅에서는 뷰의 라우터HTTP통신 방법에 대하여 알아보자.