컴포넌트
컴포넌트란 무엇인가
기본 HTML 엘리먼트를 확장해 재사용 가능 코드를 캡슐화 하는데 사용
Vue 컴파일러에 의해 동작이 추가된 사용자 지정 엘리먼트
경우에 따라 특별한 is 속성으로 확장 된 원시 HTML 엘리먼트로 나타날 수 있음
컴포넌트 사용하기
등록
//인스턴스 생성
new Vue({
el: '#some-element',
})
//전역 컴포넌트 등록
Vue.component('my-component', {
//옵션
})
//HTML
<div id="example">
<my-component></my-component>
</div>
//등록
Vue.component('my-component', {
template: '<div>사용자 정의 컴포넌트 입니다!</div>'
})
//인스턴스 생성
new Vue({
el: '#example'
})
//렌더링 후 결과
<div id="example">
<div>사용자 정의 컴포넌트 입니다!</div>
</div>
지역 등록
컴포넌트를 components 인스턴스 옵션으로 등록함으로써 범위에서만 사용할 수 있는 컴포넌트 생성 가능
var Child = {
template: '<div>사용자 정의 컴포넌트 입니다!</div>
}
new Vue({
components: {
'my-component': Child
}
})
DOM 템플릿 구문 분석 경고
DOM을 템플릿으로 사용할 때 ( el 옵션을 사용하여 기존 콘텐츠가 있는 엘리먼트를 마운트 할 때), Vue는 템플릿 콘텐츠만 가져올 수 있기 때문에 HTML이 작동하는 방식에 고유한 몇 가지 제한 사항이 적용된다. 이는 브라우저가 구문 분석과 정규화한 후에 작동합니다. 가장 중요한 것은 <ul>
, <ol>
, <table>
과 <select>
와 같은 일부 엘리먼트는 그 안에 어떤 엘리먼트가 나타날 수 있는지에 대한 제한을 가지고 있으며,<option>
과 같이 특정 다른 엘리먼트 안에만 나타날 수 있습니다.
이러한 제한이 있는 엘리먼트가 있는 사용자 지정 컴포넌트를 사용하면 다음과 같은 문제가 발생할 수 있음
<table>
<my-row>...</my-row>
</table>
이렇게 작성하면 <my-row>
는 렌더링 시 에러를 발생함 이때 is
를 사용
<table>
<tr is="my-row"></tr>
</table>
이때 다음 세가지 중 하나에 포함되면 문자열 템플릿을 사용하는 경우에는 이러한 제한 사항이 적용되지 않음
<script type="text/x-template">
- JavaScript 인라인 템플릿 문자열
- .vue 컴포넌트
따라서 가능한 경우 항상 문자열 템플릿을 사용하는 것이 좋음
data는 반드시 함수
//HTML
<div id="example-2">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>
//JavaScript
var data = { counter: 0 }
Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
// 데이터는 기술적으로 함수이므로 Vue는 따지지 않지만
// 각 컴포넌트 인스턴스에 대해 같은 객체 참조를 반환합니다.
data: function () {
return {
counter: 0
}
}
})
new Vue({
el: '#example-2'
})
컴포넌트 작성
컴포넌트는 부모-자식 관계에서 가장 일반적으로 함께 사용하기 위한 것이다.
Vue.js에서 부모-자식 컴포넌트 관계는 props
는 아래로, events
는 위로 라고 할 수 있음
=> 부모는 props
로 자식에게 데이터를 넘기고, 자식은 events
로 부모에게 메시지를 보냄
Props
Props로 데이터 전달하기
모든 컴포넌트 인스턴스는 범위가 있음.
즉, 하위 컴포넌트의 템플릿에서 상위 데이터를 직접 참조 할 수 없고,
이 때 props
옵션을 사용해 하위 컴포넌트로 전달 가능함
Vue.component('child', {
// props 정의
props: ['message'],
templage: '<span>{{ message }}</span>'
})
<child message="안녕"></child>
위의 코드에서 message
를 props
로 선언을 하면 “안녕” 이라는 결과가 출력된다.
camelCase vs kebab-case
Vue.component('child', {
// JavaScript는 camelCase
props: ['myMessage'],
template: '<span>{{ myMessage }}</span>'
})
<!-- HTML는 kebab-case -->
<child my-message="안녕하세요!"></child>
주석에 쓴것과 같이 JS는 camelCase, HTML은 kebab-case로 작성
동적 Props
v-bind
를 사용하여 부모의 데이터에 props를 동적으로 바인딩 할 수 있음
데이터가 상위에서 업데이트 될 때마다 하위 데이터로도 전달된다.
<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
위와같이 코드를 작성하면 입력창에 입력하면 아랫줄에 같은 내용이 실시간으로 바인딩된다.
리터럴 vs 동작
리터럴 구문으로 문자열을 전달할 때와 숫자를 전달할 때
//문자열 "1" 전달
<comp some-prop="1"></comp>
//숫자 1 전달
<comp v-bind:some-prop="1"></comp>
단방향 데이터 흐름
모든 props는 하위 속성과 상위 속성 사이의 단방향 바인딩 형성
즉, 상위 속성이 업데이트되면 하위로 흐르지만, 반대는 안됨
우선 prop자체의 속성을 보면
- prop은 초기값을 전달하는데만 사용되며 하위 컴포넌트는 이후에 이를 로컬 데이터 속성으로 사용하기만 함
prop은 변경되어야 할 원시 값으로 전달된다
하위 컴포넌트 내부에서 prop을 변형하려고 시도하면 안되고, 변경하고 싶을 때 사용할 두가지 방법이 있음
prop의 초기값을 초기값으로 사용하는 로컬 데이터 속성을 정의
props: ['initialCounter'],
data: function() {
return { counter: this.initialCounter}
}
prop 값으로 부터 계산된 속성을 정의함
props: ['size'],
conputed: {
normalizedSize: function() {
return this.size.trim().toLowerCase()
}
}
Prop 검증
컴포넌트가 prop에 대한 요구사항을 지정할 수 있음
요구사항이 충족되지 않으면 Vue에서 경고를 내보냄
즉, prop을 문자열 배열로 정의하는 대신 유효성 검사 요구사항이 있는 객체 사용
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
또한 커스텀 생성자 함수가 될 수 있고, assertion(정확히 무슨의민지 모르겠음)은 instanceof
체크로 만들어 질 것
prop 유효성 검사가 실패하면 Vue는 콘솔 경고 생성
사용자 정의 이벤트
prop이 부모가 자식에게 데이터를 전달하는 것이라면, 사용자 정의 이벤트로 자식은 부모에게 문제를 알림
v-on
을 이용한 사용자 지정 이벤트
모든 Vue 인스턴스는 다음과 같은 이벤트 인터페이스를 구현함
$on(eventName)
- 이벤트 감지(자식 컴포넌트에서 보낸 이벤트 감지 x)$emit(eventName)
- 이벤트 트리거
addEventListener
, dispatchEvent
와 별개
또한 부모 컴포넌트는 자식 컴포넌트가 사용되는 템플릿에서 직접 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>
//JavaScript
Vue.component('button-counter', {
template: '<button v-on:click="increment">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
increment: function () {
this.counter += 1
this.$emit('increment')
}
}
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
이때 하위 컴포넌트가 외부에서 발생하는 것과 완전히 분리된다는 점을 유의해야 함
컴포넌트에 네이티브 이벤트 바인딩
컴포넌트의 루트 엘리먼트에서 네이티브 이벤트를 수신하려 할 때
v-on
에 .native
수식자를 사용
<my-component v-on:click.native="doTheThing"></my-component>
사용자 정의 이벤트를 사용하여 폼 입력 컴포넌트 만들기
사용자 정의 이벤트는 v-model
에서 작동하는 사용자 정의 입력을 만드는데도 사용 가능
<input v-model="something">
//위,아래 코드는 같음
<input
v-bind:value="something"
v-on:input="something = $event.target.value">
컴포넌트와 같이 사용하면 다음과 같이 된다
<custom-input
:value="something"
@input="value => { something = value }">
</custom-input>
value
prop을 가진다- 새로운 값으로
input
이벤트를 내보냄
컴포넌트의 v-model
사용자 정의
기본적으로 컴포넌트의 v-model
은 value
를 보조변수로 사용하고 input
을 이벤트로 사용하지만
체크박스와 라디오 버튼과 같은 일부 입력 타입은 다른 목적으로 value
속성을 사용할 수 있음 => model
옵션 사용
//HTML
<my-checkbox v-model="foo" value="some value"></my-checkbox>
//JavaScript
Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
//다른 목적을 위해 `value` prop을 사용
value: String
},
// ...
})
아래 코드와 같다
<my-checkbox
:checked="foo"
@change="val => { foo = val }"
value="some value">
</my-checkbox>