Peter Cho

[Vue] 컴포넌트 상세 정리

2018.09.07 | 6 Minute Read

컴포넌트를 통해 해결하고 싶은 문제

  • 뷰와 데이터 로직의 재사용
  • 반응적이고 조합이 가능한 컴포넌트
  • 부모-자식 컴포넌트 간의 명확한 인터페이스 분리
  • 추론이 쉽고 유지 관리가 쉬운 의사소통
  • 추론이 쉽게 명확한 콘텐츠 배포 프로세스
  • DOM 변경 단일화(refs 객체 사용)
  • 인터페이스의 단순화 : Vue 인스턴스를 사용하여 모든 옵션 객체, 라이프 사이클 훅를 사용할 수 있다.

데이터 캡슐화 : data를 함수로 사용하는 이유

세 개의 컴포넌트 인스턴스가 모두 같은 data 객체를 공유하므로 하나의 카운터를 증가 시키면 모두 증가합니다. 그래서 새로운 데이터 객체를 반환하여 이 문제를 해결합니다.

Vue.component('my-component', {
  template: '<span></span>',
  data: {message: 'hello'}
})
<div id="example-2">
  <simple-counter></simple-counter>
  <simple-counter></simple-counter>
  <simple-counter></simple-counter>
</div>

컴포넌트 작성

컴포넌트는 부모-자식 관계에서 가장 일반적으로 함께 사용하기 위한 것이다. 컴포넌트 A는 자체 템플릿에서 컴포넌트 B를 사용할 수 있다. 그들은 필연적으로 서로 의사소통이 필요합니다. 부모는 자식에세 데이터를 전달해야 할 수도 있으며, 자식은 자신에게 일어난 일을 부모에게 알릴 필요가 있습니다.

그러나 부모와 자식이 명확하게 정의된 인터페이스를 통해 가능한한 분리된 상태로 유지하는 것도 매우 중요합니다. 이렇게하면 각 컴포넌트의 코드를 상대적으로 격리 할 수 있도록 작성하고 추론할 수 있으므로 유지 관리가 쉽고 잠재적으로 쉽게 재사용 할 수 있습니다.

VueJs에서 부모-자식 컴포넌트 관계는 props는 아래로, events 위로 라고 요약할 수 있습니다. 부모는 props를 통해 자식에게 데이터를 전달하고 자식은 events를 통해 부모에게 메시지를 보냅니다.

Props

모든 컴포넌트 인스턴스에는 자체 격리 범위가 있습니다. 즉, 하위 컴포넌트의 템플릿에서 상위 데이터를 직접 참조 할 수 없으며 그렇게 해서는 안됩니다. 데이터를 props을 사용하여 하위 컴포넌트로 전달 될 수 있습니다.

prop는 상위 컴포넌트의 정보를 전달하기 위한 사용자 지정 특성입니다. 하위 컴포넌트는 props 옵션을 사용하여 수신 할 것으로 기대되는 props를 명시적으로 선언해야 합니다.

데이터가 상위에서 업데이트 될 때마다 하위 데이터로도 전달됩니다.

하위 속성과 상위 속성 사이의 단방향 바인딩을 형성합니다. 상위 속성이 업데이트되면 하위로 흐르게 되지만 그 반대는 안됩니다. 이렇게하면 하위 컴포넌트가 실수로 부모의 상태를 변경하여 앱의 데이터 흐름을 추론하기 더 어렵게 만드는 것을 방지할 수 있습니다.

존재하는 속성 문제

대부분의 속성의 경우 컴포넌트에 제공된 값은 컴포넌트에서 설정된 값을 대체합니다. 예를 들어, type="large"가 전달되면 type="data"를 대체할 것이고 아마도 망가뜨릴 것입니다. classstyle 속성은 두 값이 합쳐져서 최종 값으로 만듭니다.

자식 컴포넌트 정취

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

비 부모-자식간 통신

부모-자식이 아닌 두 컴포넌트가 서로 통신이 필요할 때 Vue 인스턴스를 중앙 이벤트 버스로 사용할 수 있습니다.

var bus = new Vue()
// 컴포넌트 A의 메소드
bus.$emit('id-selected', 1)
// 컴포넌트 B의 created 훅
bus.$on('id-selected', function (id) {
  // ...
})

콘텐츠 배포 프로세스

컴포넌트를 사용할 때 다음과 같이 컴포넌트를 구성하는 것이 좋습니다.

<app>
  <app-header></app-header>
  <app-footer></app-footer>
</app>

주목해야할 두가지 사항이 있습니다.

  1. <app> 컴포넌트는 어떤 컨텐츠를 받을 지 모릅니다. 그것은 <app>이 사용하는 컴포넌트에 의해 결정됩니다.
  2. <app> 컴포넌트에는 자체 템플릿이 있을 가능성이 큽니다.

위 구성으로 작동하도록 하려면 부모 콘텐츠와 컴포넌트의 자체의 템플릿을 섞는 방법이 필요합니다. 이것은 콘텐츠 배포 프로세스입니다. VueJS는 현재 웹 컴포넌트 사양 초안을 모델로 한 콘텐츠 배포 API를 구현하며 원본 콘텐츠의 배포판 역할 하기 위해 특수한 <slot> 엘리먼트를 사용합니다.

범위 컴파일

컴파일되는 범위를 명확히 해야 합니다. 다음과 같은 템플릿이 있다고 상상해보겠습니다.

<child-component></child-component>

message가 부모 데이터 또는 자식 데이터 중 부모에 바인딩되어야 합니다. 컴포넌트 범위에 대한 간단한 법칙은 다음과 같습니다.

  1. 상위 템플릿의 모든 내용은 상위 범위로 컴파일됩니다.
  2. 하위 템플릿의 모든 내용은 하위 범위에서 컴파일됩니다.

일반적인 실수는 부모 템플릿의 하위 속성/메소드에 디렉티브를 바인딩하려고하는 것입니다.

<child-component v-show="someChildProperty"></child-component>

someChildProperty가 자식 컴포넌트의 속성이라고 가정하면, 위의 예제는 작동하지 않을 것입니다.

단일 슬롯: 부모에 콘텐츠가 있을 때

하위 컴포넌트 템플릿에 최소한 하나의 <slot> 콘텐츠가 포함되어 있지 않으면 부모 콘텐츠가 삭제 됩니다. 속성이 없는 슬롯이 하나 뿐인 경우 전체 내용 조각이 DOM의 해당 위치에 삽입되어 슬롯 자체를 대체합니다.

원래 <slot>태그 안에 있는 내용은 대체 콘텐츠로 간주됩니다. 대체 콘텐츠는 하위 범위에서 컴파일 되며 호스팅 엘리먼트가 비어 있고 삽입할 콘텐츠가 없는 경우에만 표시됩니다.

my-component라는 컴포넌트가 있다고 가정

<div>
  <h2>나는 자식 컴포넌트의 제목입니다</h2>
  <slot>
    제공된 컨텐츠가 없는 경우에만 보실 수 있습니다.
  </slot>
</div>

컴포넌트를 사용하는 부모 컴포넌트

<div>
  <h1>나는 부모 컴포넌트의 제목입니다</h1>
  <my-component>
    <p>이것은 원본 컨텐츠 입니다.</p>
    <p>이것은 원본 중 추가 컨텐츠 입니다</p>
  </my-component>
</div>

아래처럼 렌더링 됩니다.

<div>
  <h1>나는 부모 컴포넌트의 제목입니다</h1>
  <div>
    <h2>나는 자식 컴포넌트의 제목 입니다</h2>
    <p>이것은 원본 컨텐츠 입니다.</p>
    <p>이것은 원본 중 추가 컨텐츠 입니다</p>
  </div>
</div>

이름을 가지는 슬롯 : 여러개의 콘텐츠가 있는 경우

슬롯의 이름을 지정하여 어떻게 내용을 배포해야 하는 지를 커스터마이징을 할 수 있다. 명명되지 않은 슬롯은 하나만 사용되고 콘텐츠의 포괄적인 컨텐츠 역할을 합니다.

범위를 가지는 슬롯 : 행위와 표현을 분리하고 싶을 경우

어려워서 작성안함…

다수 컴포넌트 중 상태에 매칭되는 컴포넌트를 사용해야 하는 경우

같은 마운트 포인트를 사용하고 예약된 <component> 엘리먼트를 사용하여 어러 컴포넌트 간의 동적으로 트랜지션하고 is 속성에 동적으로 바인드 할 수 있습니다.

var vm = new Vue({
  el: '#example',
  data: {
    currentView: 'home'
  },
  components: {
    home: { /* ... */ },
    posts: { /* ... */ },
    archive: { /* ... */ }
  }
})
<component v-bind:is="currentView">
  <!-- vm.currentView가 변경되면 컴포넌트가 변경됩니다! -->
</component>

재사용 가능한 컴포넌트 제작하기

컴포넌트를 작성할 때 나중에 다른 곳에서 다시 사용할 것인지에 대한 여부를 명심하는 것이 좋습니다. 일회용 컴포넌트가 단단히 결합 되어도 상관 없지만 재사용 가능한 컴포넌트는 깨끗한 공용 인터페이스를 정의 해야하며 사용된 컨텍스트에 대한 가정을 하지 않아야합니다. Vue 컴포넌트의 API는 Props, Event, Slot의 세 부분으로 나뉩니다.

  • Props는 외부 환경이 데이터를 컴포넌트로 전달하도록 허용합니다.
  • Event를 통해 컴포넌트가 외부 환경에서 사이드 이펙트를 발생할 수 있도록 합니다.
  • Slot을 사용하면 외부 환경에서 추가 컨텐츠가 포함 된 컴포넌트를 작성할 수 있습니다.

자식 컴포넌트 참조

propsevent가 있었음에도 불구하고 때때로 Javascript로 하위 컴포넌트에 직접 액세스 해야 할 수도 있습니다. 이를 위해 ref를 사용하여 참조 컴포넌트 ID를 자식 컴포넌트에 할당해야 합니다.

ref엘리먼트 또는 자식 컴포넌트에 대한 참조를 등록하는 데 사용합니다. 일반 DOM 엘리먼트에 사용되는 경우 참조는 해당 엘리먼트입니다. 하위 컴포넌트에서 사용되는 경우 참조는 컴포넌트 인스턴스입니다.

참조 자체는 렌더링 함수의 결과로 생성되기 때문에 초기 렌더링에서 참조 자체를 엑세스 할 수 없습니다. 그리고 반응이 없으므로 데이터 바인딩을 위해 템플릿에서 사용해서는 안됩니다.

비동기 컴포넌트

대규모 응용 프로그램에서 응용 프로그램을 더 작은 덩어리로 나누고 실제로 필요할 때만 서버에서 컴포넌트를 로드해야 할 수도 있습니다. Vue를 사용하면 컴포넌트 정의를 비동기식으로 해결하는 팩토리 함수로 컴포넌트를 정의 할 수 있습니다. Vue는 컴포넌트가 실제로 렌더링되어야 할 때만 팩토리 기능을 트리거하고 이후의 리렌더링을 위해 결과를 캐시합니다.

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 컴포넌트 정의를 resolve 콜백에 전달합니다.
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})

정적 콘텐츠가 많이 포함된 컴포넌트인 경우

일반 HTML 엘리먼트를 렌더링하는 것은 Vue에서 매우 빠르지만 가끔 정적 콘텐츠가 많이 포함된 컴포넌트가 있을 수 있습니다. 이런 경우, v-once 디렉티브를 루트 엘리먼트에 추가함으로써 캐시가 한번만 실행되도록 할 수 있습니다.

Vue.component('terms-of-service', {
  template: '\
    <div v-once>\
      <h1>Terms of Service</h1>\
      ... a lot of static content ...\
    </div>\
  '
})