본문 바로가기
front-end/vue.js

처음 시작하는 Vue.js (1) Vue의 정의와 뷰 컴포넌트

by MOOB 2019. 8. 31.

Vue.js의 특징

  • MVVM 패턴을 사용(비즈니스 로직이나 백엔드 로직으로 분리 사용)
  • Vue.js는 컴포넌트 기반 프레임 워크이다.
  • 코드를 재사용하기 쉽고 코드를 직관적으로 파악할 수 있다.
  • 양방향 데이터 바인딩 (화면에 표시되는 값과 프레임 워크의 모델 데이터 값이 동기화 됨)
  • 데이터 전달할 때 항상 상위 컴포넌트에서 하위 컴포넌트 방향으로만 전달함.

Vue.js의 기본적인 구조

new Vue()인스턴스를 생성할 때 Vue를 생성자라고 한다. 생성자란 객체를 새로 생성할 때 자주 사용하는 옵션과 기능을 미리 특정 객체에 저장해 기능 확장을 도와준다.

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <div id="app">
      {{ message }}
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script>
    new Vue ({ // 뷰 인스턴스 생성자
      el: '#app', // 뷰 인스턴스가 그려질 위치
      data: {
        message: 'Hello'
      }
    });
  </script>
  </body>
</html>

뷰 인스턴스의 라이프 사이클

  • beforeCreate : 인스턴스가 생성되고 나서 가장 처음으로 실행되는 라이프 사이클 단계 data 또는 method 속성이 아직 인스턴스에 정의되지 않고 돔과 같은 화면 요소에도 접근할 수 없다.
  • created : data속성과 method 속성이 정의 되었기 때믄에 이와 관련된 메소드를 실행할 수 있으나 template 속성이 정의되기 전이므로 돔 요소엔 접근할 수 없다. 가장 첫 라이프 사이클 단계이고 컴포넌트가 생성된 후 실행되는 단계이므로 서버에 데이터를 요청하여 받아오는 로직을 수행하기 좋음.
  • beforeMount : javascript가 돔을 그리기 이전, render() 함수가 호출되기 직전의 로직을 추가하기 좋다.
  • mounted: el 속성에서 지정한 화면 요소에 인스턴스가 부착되고 나서 호출되는 단계, template에서 정의한 화면 요소에 접근할 수 있어 화면 요소를 제어하는 로직을 수행할 수 있다.
  • beforeUpdate: $watch를 통해 관찰중인 데이터가 변경되면 가상 돔으로 화면을 다시 그리기 전에 호출되는 단계, 변경 예정인 새 데이터에 접근할 수 있어 변경 예정인 데이터의 값과 관련된 로직을 미리 넣을 수 있다.
  • updated: 데이터가 변경되고 나서 가상돔으로 다시 화면을 그리고 실행되는 단계, 데이터 변경 후 화면요소와 제어와 관련된 로직을 추가하는 게 좋음. 데이터 값을 갱신하는 로직은 beforeUpdated에 추가하고 변경 데이터의 화면 요소와 관련된 로직을 updated에 추가하는 게 좋습니다.
  • beforeDestroyed: 뷰 인스턴스의 데이터를 삭제하기 좋은 단계

다음 예제는 뷰의 라이프 사이클을 알 수 있는 예제이다. 위 설명처럼 updated는 데이터 갱신이 있어야 동작하기 때문에 mounted에서 데이터 갱신을 해놨다.

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <div id="app">
      {{ message }}
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script>
    new Vue ({
      el: '#app',
      data: {
        message: 'Hello'
      },
      beforeCreat : function(){
        console.log("beforeCreat");
      },
      created : function(){
        console.log("created");
      },
      mounted : function(){
        console.log("mounted");
        this.message = 'Hello Vue!';
      },
      updated: function(){
        console.log("updated");
      }
    });
  </script>
  </body>
</html>

뷰 컴포넌트

컴포넌트란 조합하여 화면을 구성할 수 있는 블록을 말한다. 화면의 영역을 컴포넌트로 쪼개 관리하면 나중에 코드 재사용 하기 편해진다. 컴포넌트는 전역 또는 지역 컴포넌트로 제작된다.

전역 컴포넌트

전역 컴포넌트의 구성은 다음과 같다. 컴포넌트의 내에는 template, method, data등 인스턴스 옵션 속성을 정의 할 수 있다.

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <div id="app">
      <button>전역 컴포넌트 등</button>
      <global></global>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script>
    Vue.component('global', {
      template : '<div>전역 컴포넌트 입니당</div>'
    });

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

지역 컴포넌트

지역 컴포넌트의 등록은 인스턴스에 components 속성을 추가하고 등록할 컴포넌트의 이름과 내용을 정의한다.

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <div id="app">
      <button>전역 컴포넌트 등</button>
      <local></local>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script>
    var cmp = {
      template : '<div>지역 컴포넌트 입니당</div>'
    };


    new Vue({
      el: '#app',
      components : {
        'local' : cmp
      }
    });

  </script>
  </body>
</html>

전역 컴포넌트는 인스턴스를 생성할 때마다 components 속성으로 등록할 필요 없이 한 번 생성하면 어느 인스턴스에서도 사용할 수 있다.

뷰 컴포넌트 통신

뷰는 컴포넌트를 통해 화면을 구성하고, 컴포넌트는 고유의 스코프를 가지므로 같은 웹페이지라도 데이터를 공유할 수 없다. 뷰의 가장 기본적인 데이터 전달 방식은 상위 - 하위 컴포넌트 간의 데이터 전달이다.

 

 

위 그림과 같이 상위 컴포넌트가 자식 컴포넌트로 prop를 전달하고 하위 컴포넌트가 상위 컴포넌트로 이벤트를 전달하는 구조를 가지고 있다.

props 속성

Props 속성은 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달할 때 사용하는 속성이다. v-bind에 하위 컴포넌트에서 정의한 속성 이름을 넣고 오른쪽 값에는 상위 컴포넌트의 data 속성 값을 넣는다.

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <div id="app">
      <child-component v-bind:propsname = "message"></child-component>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script>
    Vue.component('child-component', {
      props : ['propsname'], // props 이름
      template : '<p>{{ propsname }}</p>'
// 이 모양에 데이터 들어옴
    });

    new Vue({
      el: '#app',
      data : {
        message : 'this is from parent component'
// 상위로 전달될 데이터
      }
    });
  </script>
  </body>
</html>

emit event

하위 컴포넌트에서 상위 컴포넌트로의 통신은 이벤트 전달을 통해 이루어진다. 그러나 기본적으로 뷰는 상 > 하로의 단방향 통신을 지향하므로 하위 컴포넌트에서 특정 이벤트가 발생하면 상위 컴포넌트에서 해당 이벤트를 수신하여 메소드로 호출하는 방식을 취한다.

다음은 v-on속성을 통해 하위 컴포넌트에서 클릭 이벤트가 발생시키고 발생 이벤트를 통해 상위 컴포넌트의 printText() 메서드를 실행시킨다.

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <div id="app">
      <child-component v-on:event-name="printText"></child-component>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script>
    Vue.component('child-component', {
      template : '<button v-on:click="showLog">show</button>',
      methods: {
        showLog : function(){
          this.$emit('event-name');
        }
      }
    });

    new Vue({
      el: '#app',
      data : {
        message : 'this is from parent component'
      },
      methods: {
        printText : function(){
          console.log("recieved event");
        }
      }
    });
  </script>
  </body>
</html>

동레벨간의 통신

앞서 설명한 대로 뷰는 기본적으로 상위에서 하위 컴포넌트로의 단방향 데이터 통신을 지향하고 있기 때문에 같은 레벨의 컴포넌트 간의 데이터 통신을 위해서는 공통 상위 컴포넌트로 이벤트를 전달한 후 그 상위 컴포넌트에서 하위로 props를 내려보내야 한다.

그러나 이 방법을 사용하지 않기 위해 최근 만들어진 것이 이벤트 버스이다.

이벤트 버스

이벤트 버스는 로직을 담은 인스턴스와는 별개로 새로운 인스턴스를 하나 더 생성하고 새 인스턴스를 이용하여 이벤트를 보내고 받는다. 이벤트를 보내는 컴포넌트는 $emit 을 이벤트를 받는 컴포넌트에서는 $on을 통해 구현한다.

이는 props를 사용하지 않고 직접적으로 이벤트 처리할 수 있어 편리하지만 이벤트 버스를 남발할 경우 직관적으로 데이터의 이동을 알 수 없어 데이터를 관리하기 어려울 수 있다.

다음 예제는 하위 컴포넌트에서 상위 컴포넌트로 이벤트 버스를 사용하여 데이터를 전달하는 코드이다.

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <div id="app">
      <child-component></child-component>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script>
    var eventBus = new Vue(); // 이벤트 버스로 사용할 새 인스턴스 생 

    Vue.component('child-component', {
      template : '<div>하위 컴포넌트 영역입니당.</div><button v-on:click="showLog">show</button>',
      // 생성된 template는 하나의 node만 가짐
      methods: {
        showLog : function(){
          eventBus.$emit('triggerEventBus', 100);
        }
      }
    });

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

댓글