600.jpg

컴포넌트란?

  • Vue.js가 제공하는 가장 강력한 기능 중 하나
  • 컴포넌트는 HTML 마크업, 자바스크립트 로직을 포함한 하나의 덩어리
  • 캡슐화가 자연스럽게 가능해지고 따라서 재사용이 가능해짐

컴포넌트 사용하기

  • 컴포넌트는 Vue 오브젝트를 확장한 오브젝트
  • 컴포넌트를 생성하면 확장된 Vue 인스턴스가 생성

글로벌 컴포넌트

  • 컴포넌트를 생성하는 것은 Vue 인스턴스를 생성하는 것과 비슷
new Vue({
  el: '#some-element',
  // 옵션
})
Vue.component('my-component', {
  // 옵션
})
  • Vue.component는 컴포넌트를 글로벌하게 등록하는 메서드
  • 주의 : 확장된 Vue인스턴스, 즉 컴포넌트에서 data를 정의할때 반드시 함수로 정의해야함
  • Tip: 컴포넌트 이름을 하이픈을 포함한 소문자여야 함

로컬 컴포넌트

  • 컴포넌트를 Vue인스턴스 혹은 컴포넌트의 옵션으로 등록해 해당 컴포넌트 내부에서만 사용하도록 지정
var Child = {
  template: '<div>사용자 정의 컴포넌트 입니다!</div>'
}

new Vue({
  // ...
  components: {
    // <my-component> 는 상위 템플릿에서만 사용할 수 있습니다.
    'my-component': Child
  }
})

제한사항

  • ul, ol, table, select와 같은 일부 엘리먼트 내부에서는 컴포넌트를 사용할 수 없습니다.
  • 제한 있는 엘리먼트 내부에 컴포넌트를 사용할 경우 문제 발생 > is 옵션을 사용해 정상적으로 렌더링 할 수 있음
// 에러 발생
  <table>
        <my-row>...</my-row>
  </table>

//해결
  <table>
        <tr is="my-row"></tr>
  </table>
  • 다음 소스 중 하나에 포함되면 문자열 템플릿을 사용하는 경우에는 이러한 제한 사항이 적용되지 않습니다.
  • <script type="text/x-template">
  • JavaScript 인라인 템플릿 문자열
  • .vue 컴포넌트
  • 가능한 경우 항상 문자열 템플릿을 사용하는 것이 좋음

템플릿 분리

  • Tip : 컴포넌트의 템플릿은 맨 상위에 하나의 엘리먼트만 존재해야 합니다.

데이터 전달

  • 컴포넌트는 자식 컴포넌트 혹은 부모 컴포넌트에게만 데이터를 전달할 수 있습니다.

부모-자식 컴포넌트 관계

  • 부모 컴포넌트가 자식 컴포넌트에게 데이터를 전달하는 것이 가능
  • 자식 컴포넌트는 부모 컴포넌트에게 데이터를 전달하는 것은 불가능
  • 자식 컴포넌트가 부모 컴포넌트에게 이벤트를 전달해 부모 컴포넌트에게 메시지를 전달 가능
    props-events.png
    • Vue.js에서 부모-자식 컴포넌트 관계는 props는 아래로, events 위로 라고 요약
    • 부모는 props를 통해 자식에게 데이터를 전달하고 자식은 events를 통해 부모에게 메시지를 보냄

props

  • 자식 컴포넌트가 부모 컴포넌트의 데이터를 참조하기 위해서는 props 옵션을 사용해 부모컴포넌트가 자식 컴포넌트에게 데이터를 전달 가능
  • 자식 컴포넌트는 props를 사용해 받을 것으로 기대하는 데이터를 미리 명시적으로 선언해야함
Vue.component('child', {
  // props 정의
  props: ['message'],
  // 데이터와 마찬가지로 prop은 템플릿 내부에서 사용할 수 있으며
  // vm의 this.message로 사용할 수 있습니다.
  template: '<span>{{ message }}</span>'
})

//그런 다음 일반 문자열을 다음과 같이 전달
<child message="안녕하세요!"></child>

네이밍 규칙

  • 두단어로 이루어진 props의 경우 템플릿 내에 속성으로 적을 때에는 kebab-case를 사용
  • kebeb-case : 하이픈으로 구분하는 방법
  • 스크립트 내부에서는 camelCase를 사용
Vue.component('child', {
  // JavaScript는 camelCase
  props: ['myMessage'],
  template: '<span>{{ myMessage }}</span>'
})

<!-- HTML는 kebab-case -->
<child my-message="안녕하세요!"></child>

동적 props

  • 정적 props : String 타입 외에 Number, Object, List 데이터는 전달할 수 없음. 또한 전달된 데이터는변경할 수 없음
  • 동적으로 데이터를 전달하고 싶다면 v-bind를 이용해 데이터를 전달
  • 부모 컴포넌트의 message가 변경된 경우 자식 컴포넌트는 다시 message 데이터를 받아 다시 렌드링
<div>
  <input v-model="parentMsg">
  <br>
  <child v-bind:my-message="parentMsg"></child>
</div>

//v-bind에 대한 단축 구문을 사용하는 것이 더 간단
<child :my-message="parentMsg"></child>

주의점

  • props를 변경하고 싶다면
    1. props의 초기 값을 컴포넌트 로컬 데이터로 정의
    2. props로부터 계산된 속성을 정의

props 검증하기

  • props검증을 통해 props 데이터 타입을 강제
  • 다른 개발자와 협업할 때 중요
Vue.component('example', {
  props: {
    // 기본 타입 확인 (`null` 은 어떤 타입이든 가능하다는 뜻입니다)
    propA: Number,
    // 여러개의 가능한 타입
    propB: [String, Number],
    // 문자열이며 꼭 필요합니다
    propC: {
      type: String,
      required: true
    },
    // 숫자이며 기본 값을 가집니다
    propD: {
      type: Number,
      default: 100
    },
    // 객체/배열의 기본값은 팩토리 함수에서 반환 되어야 합니다.
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    // 사용자 정의 유효성 검사 가능
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }
})
  • type은 다음 네이티브 생성자 중 하나를 사용

    • String
    • Number
    • Boolean
    • Function
    • Object
    • Array
    • Symbol

사용자 정의 이벤트

  • 자식 컴포넌트는 부모 컴포넌트에게 데이터를 전달할 수는 없지만 이벤트를 통해 메시지를 전달할 수 있음

v-on을 이용한 사용자 지정 이벤트

  • 자식 컴포넌트는 $emit을 이용해 이벤트를 실행
  • Vue 인터페이스는 다음과 같은 이벤트 인터페이스를 구현
    • $on(eventName)을 사용하여 이벤트를 감지 하십시오.
    • $emit(eventName)을 사용하여 이벤트를 트리거 하십시오.

Vue의 이벤트 시스템은 브라우저의 EventTarget API와 별개입니다. 비슷하게 작동하지만 $on 과 $emit 는 addEventListener 와 dispatchEvent의 별칭이 아닙니다.

  • 부모 컴포넌트는 자식 컴포넌트가 사용되는 템플릿에서 직접 v-on 을 사용하여 자식 컴포넌트에서 보내진 이벤트를 청취

$on은 자식에서 호출한 이벤트는 감지하지 않습니다. v-on을 템플릿에 반드시 지정해야 합니다. 아래의 예제를 보십시오.

//html 
<div id="counter-event-example">
  <p>{{ total }}</p>
  <button-counter v-on:increment="incrementTotal"></button-counter>
  <button-counter v-on:increment="incrementTotal"></button-counter>
</div>
//js
Vue.component('button-counter', {
  template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    incrementCounter: function () {
      this.counter += 1
      this.$emit('increment')
    }
  },
})

new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1
    }
  }
})

.sync

  • 양방향 바인딩
  • <input :value.sync="message">
  • .sync 수식어는 일종의 Syntax Sugar라고 볼 수 있음
  • 자식 컴포넌트가 .sync를 가지는 속성을 변경하면 값의 변경이 부모에 반영