[Vue.js] 2) 컴포넌트

Yoojin Jeong·2021년 2월 8일
0

컴포넌트란?

  • Vue.js는 컴포넌트들을 조합해 전체 애플리케이션을 작성한다.
  • 컴포넌트는 부모-자식 관계로 트리 구조를 형성한다.
  • 부모(상위)컴포넌트는 자식 컴포넌트로 정보(props)를 전달하고, 자식(하위)컴포넌트는 부모 컴포넌트로 이벤트를 전달한다.
  • .sync 수식어를 통해 양방향 정보 전달이 가능하지만 권장하지 않는 방법이므로 위와 같이 알아두는 것이 좋다. (버전에 따라 상이하고, 일관적이고 명백하지 않기 때문)

컴포넌트 정의 방법

컴포넌트 등록

Vue.component 메서드를 사용해서 전역에 등록하면 자동으로 모든 컴포넌트에서 사용할 수 있다. 메서드의 매개변수로 다음과 같은 정보가 전달된다.
1. 사용자 정의 태그로 사용할 이름
2. 컴포넌트의 옵션 객체

Vue.component('my-component',{
 template: '<p>MyComponent</p>'
})

등록된 컴포넌트 사용하기

<div id="app">
  <my-component></my-component>
</div>

로컬에 등록하기
컴포넌트 정의를 특정 컴포넌트의 components 옵션에 등록하면, 로컬에 등록된 해당 컴포넌트 스코프 내부에서만 해당 컴포넌트를 사용할 수 있도록 제한할 수 있다.

//컴포넌트 정의하기
var myComponent = {
  template: '<p>MyComponent</p>'
}
new Vue({
  el: '#app',
  components: {
  //<my-component>가 루트에서만 사용할 수 있게 됨
  'my-component':myComponent

컴포넌트의 옵션

루트 생성자 new Vue()의 옵션과 마찬가지로 컴포넌트 전용 템플릿 이외에 데이터와 메서드도 정의할 수 있다.

Vue.component('my-component',{
  // 템플릿
  template: '<p>{{ message }} </p>',
  // 데이터는 객체를 리턴하는 함수로 지정하기
  data:function(){
    return{
      message:'Hello Vue.js'
    }
  },
  methods:{
  // 메서드, 산출 속성, 워처의 정의 방법은
  // 루트 생성자 객체와 같음
  }
})
  • data는 함수여야 함
    컴포넌트의 옵션은 루트 생성자의 옵션과 거의 비슷하지만 데이터는 객체를 리턴하는 함수로 정의해야 한다. 여러 개의 컴포넌트 인스턴스들이 같은 객체를 참조해서 상태를 공유되는 것을 막기 위함이다.
  • 루트 요소는 반드시 하나여야 한다.
    템플릿의 루트 요소는 반드시 하나여야 한다. 여러 개의 요소가 있는 경우, 전체를 다른 어떤 요소로 감싸줘야한다.
template : '<div><span>Hello</span><span>Vue.js!</span></div>

컴포넌트끼리의 통신

컴포넌트 인스턴스는 각각 스코프를 가지고 있다. 스코프란, 영향을 미칠 수 있는 범위를 나타낸다. 간단하게 정의한 데이터,메서드,템플릿이 스코프라고 할 수 있다. 스코프 내부의 데이터와 메서드는 this를 사용해서 접근할 수 있다.

this.message // 스코프 내부의 데이터
this.update() // 스코프 내부의 메서드

스코프는 실수로 다른 기능에 영향을 미치지 않게 하기 위한 것으로, 다른 컴포넌트에 있는 데이터와 메서드에 직접 접근할 수 없게 한다.

컴포넌트끼리 데이터를 공유하거나 연동하려면
1. props와 사용자 정의 이벤트를 사용해서 부모 자식끼리 통신하기
2. 부모 자식 관계가 아닌 경우 이벤트 버스를 사용해서 통신하기
3. Vuex를 사용한 상태 관리

부모 자식 컴포넌트

템플릿에서 다른 컴포넌트를 사용하면 부모-자식 관계가 만들어진다.

Vue.component('my-component',{
  template: '<p><comp-child></comp-child></p>'
})

//'comp-child'는 'my-component'의 자식 컴포넌트

루트 인스턴스를 부모로 지정하는 경우가 있는데, 이러한 경우에도 루트 컴포넌트를 '부모 컴포넌트'라고 부른다

<div id="app">
  <comp-child></comp-child>
</div>

comp-child컴포넌트의 템플릿에서 추가적으로 다른 컴포넌트를 사용할 수도 있다. 이렇게 네스트된 컴포넌트는 DOM처럼 트리구조를 갖게 된다.

부모 자식끼리의 데이터 흐름

부모 자식끼리 데이터를 주고받는 것은 'props'와 '사용자 정의 이벤트'를 사용해서 의존성이 최대한 적은 인터페이스를 만든다.

부모에서 자식으로
부모 컴포넌트의 템플릿에서 자식 컴포넌트를 사용할 때, 속성으로 컴포넌트에 데이터를 가지도록 할 수 있다.

<comp-child val="자식1"></comp-child>
//부모측의 템플릿

자식 컴포넌트에서 props 옵션으로 받을 속성 이름을 지정한다. 부모에서 정의한 속성을 자식이 props로 받아서 자신의 데이터처럼 사용할 수 있다.

Vue.component('comp-child',{
  // 템플릿에서 val 사용하기
  template: '<p>{{ val }} </p>',
  // 받은 속성 이름 지정하기
  props: [ 'val' ]
})

단방향이므로 props를 사용해서 자식 쪽에서 부모 쪽으로 데이터를 전달할 수는 없다.

props로 전달받은 데이터는 마음대로 변경하면 안 됨
메서드 내부에서는 this를 사용해서 자기 자신의 데이터처럼 사용할 수 있다. props는 리액티브 상태이므로 부모 쪽에서 데이터를 변경하면 자식 쪽의 상태도 변경된다.
만약 자식 컴포넌트에서 데이터 변경을 추가하고 싶은 경우는 산출 속성을 사용해서 새로운 데이터를 생성해야 한다. 원래 데이터 자체를 변경해야 하는 경우, $emit을 사용해서 부모에 액션을 일으켜 부모 쪽을 변경한다.

props로 받을 자료형 지정하기
props는 받을 자료형 이외에도 디폴트 값을 지정할 수 있고 필수 항목으로 지정하거나 조건을 만족시키지 않을 경우에 경고 출력이 가능하다.

자식에서 부모로
자식 컴포넌트의 상태에 따라 부모 컴포넌트에서 어떤 액션을 실시하도록 처리하거나, 자식 컴포넌트가 가진 데이터를 부모 컴포넌트에 전달하고 싶을 때는 사용자 정의 이벤트$emit이라는 인스턴스 메서드를 사용한다.

// 부모 측의 템플릿
<comp-child v-on:childs-event="parentMethod"></comp-child>
 child-event가 발생하면 parentMethod를 호출하도록 예약
// 자식이 자신의 이벤트 실행하기
Vue.component('comp-child',{
  template: '<button v-on:click="handleClick">이벤트 호출하기</button>',
  methods:{
    // 버튼 클릭 이벤트 핸들러에서 childs-event 호출하기
    handleClick:function(){
	this.$emit('childs-event')
    }
  }
})

부모에서 자식 자용자 정의 태그를 작성할 때 v-on 디렉티브로 이벤트를 핸들한다.
자식은 적당한 시점에서 $emit을 사용해서 자신의 이벤트를 실행한다. 이 때 $emit의 두 번째 이후의 매개변수로 임의의 데이터를 지정할 수 있다.
부모는 자식 이벤트를 통지받아 미리 등록한 핸들러를 호출한다.

// 부모 쪽에서 이벤트 받기
new Vue({
  el: '#app',
  methods:{
    // childs-event가 호출된 경우
     parentsMethod: function(){
       alert('이벤트를 받았습니다!')
     }
  }
})

자식 컴포넌트의 버튼을 클릭하면 부모 컴포넌트에서 등록한 핸들러가 호출되어 경고가 출력된다.

이벤트 버스를 이용한 정보 전달
부모 자식 컴포넌트가 아닌 컴포넌트들끼리 데이터를 전달할 경우, 'this를 사용한 props'와 '사용자 정의 이벤트'로 데이터를 주고받을 수 없다. 이러한 경우에는 Vue 인스턴스의 이벤트 버스 라는 기능을 사용한다.

// 이벤트 버스 전용 인스턴스 만들기
var bus = new Vue()
// 컴포넌트A의 메서드에서
bus.$emit('bus-event')
// 컴포넌트B의 created 훅에서
bus.$on('bus-event',function(){
// bus-event 이벤트가 발생할 때 처리
// 익명 함수 내부의 this는 bus 인스턴스
})

이벤트 버스에 데이터를 가지게 하면, 상태를 공유할 수 있다.

var bus = new Vue({
  data:{
    count :0
  }
})
Vue.component('component-b',{
  template:'<p>bus:{{ bus.count }}</p>',
  computed: {
    // bus 데이터를 산출 속성에서 사용하기
    bus: function() { return bus.$data }
  },
  created: function(){
    bus.$on('bus-event', function(){
      this.count++
    })
  }
})

많이 사용하면 인스턴스와 이벤트가 섞여서 코드가 복잡해진다. 따라서 복잡한 상태 관리를 해야 하는 경우는 Vuex를 사용해야 한다.

slot

slot은 부모가 되는 컴포넌트 측에서 자식이 되는 컴포넌트의 템플릿 일부를 끼워넣는 기능이다. 컴포넌트에 콘텐츠를 넣거나, 템플릿의 일부를 원하는 형태로 변경하고 싶을 때 사용한다.

이름있는 슬롯

이름을 붙여서 여러 개의 슬롯을 지정할 수 있다.

스코프 슬롯

슬롯은 콘텐츠를 정의한 측의 스코프를 유지한다. 즉, 자식 측의 스코프에는 접근할 수 없다. 특별한 속성인 slot-scope를 사용하면 슬롯 콘텐츠 정의에 필요한 데이터를 자식 컴포넌트에서 받아서 사용할 수 있다. 자식 측에서 <slot?>태그를 작성할 때 속성을 사용해서, 부모에 전달할 데이터를 지정한다.

컴포넌트의 양방향 데이터 바인딩

컴포넌트의 v-model

v-model을 사용해서 컴포넌트에 데이터 바인딩하면 input 이벤트를 $emit할 수 있다.
v-model의 값은 어디에?
디폴트로는 v-model의 현재 속성은 value가 되지만, 이는 props를 사용해서 명시적으로 전달해야만 사용할 수 있다.
사용자 정의 v-model
컴포넌트에 있는 v-model은 디폴트 설정으로서 value를 속성으로, input을 이벤트로 사용한다. value 속성을 다른 목적으로 사용하거나, 다른 이벤트로 사용하고 싶은 경우에는 model 옵션을 사용해서 설정을 원하는 대로 변경할 수 있다.

/### template

이외의 기능과 옵션

함수형 컴포넌트

functional 옵션을 사용해서 상태와 인스턴스를 가지지 않는 함수 형태의 컴포넌트를 정의할 수 있다. 라이프 사이클이 없고 감시가 이루어지지 않으므로 성능면에서도 좋다. 데이터는 사용할 수 없어도 props는 사용할 수 있으므로, 꽤 많은 경우에 활용할 수 있다.

동적 컴포넌트

특별한 속성으로 is가 있다. 이 is에 컴포넌트를 지정하면 특정 요소와 연결할 컴포넌트를 변경할 수 있다.

컴포넌트의 라이프 사이클

컴포넌트 인스턴스에도 각각의 라이프 사이클이 존재한다. v-if 디렉티브에 따라서 컴포넌트의 렌더링 상태가 변경되거나, 동적 컴포넌트 요소가 변경되는 시점에 컴포넌트의 인스턴스가 제거되면, 라이프 사이클이 초기화된다. props를 포함하는 컴포넌트의 상태가 변경되면 update 계열의 훅이 호출된다.
컴포넌트의 v-if 디렉티브를 사용했을 때 컴포넌트 인스턴스가 제거될지는 v-if를 작성하는 위치에 따라 달라진다.

keep-alive로 상태 유지하기

일반적으로 v-if와 동적 컴포넌트 등의 변경이 일어나면 인스턴스가 제거되고 상태가 초기화된다. 내장 컴포넌트 <keep-alive?>를 사용하면 렌더링되지 않더라도 컴포넌트의 상태를 유지할 수 있다.

0개의 댓글