본문 바로가기

프로그래밍(TA, AA)/자바스크립트

[Vue.js] 공식 가이드 문서 요약 (2) - 컴포넌트

컴포넌트는 Vue의 핵심기능 중 하나로 기본 HTML 엘리먼트를 확장하여 재사용 가능한 코드를 캡슐화하는데 도움이 된다. Vue의 컴파일러에 의해 동작이 추가된 사용자 지정 엘리먼트이다.


Vue 컴포넌트는 Vue 인스턴스이기도 하다. 루트에만 사용하는 옵션을 제외하고 모든 옵션 객체를 사용할 수 있으며 같은 라이프사이클 훅을 사용할 수도 있다.



[컴포넌트 사용하기]

전역 컴포넌트를 등록하려면, Vue.component(tagName, options)를 사용한다.

Vue.component('my-component', {
	// 옵션
})

일단 등록되면, 컴포넌트는 인스턴스의 템플릿에서 커스텀 엘리먼트, <my-component></my-component>로 사용할 수 있다. 루트 Vue 인스턴스를 인스턴스화하기 전에 컴포넌트가 등록되어 있는지 확인한다.

<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>는 상위 템플릿에서만 사용할 수 있음
		'my-component': Child
	}
})


Vue 생성자에 사용할 수 있는 대부분의 옵션은 컴포넌트에서 사용할 수 있다. 이때, data는 함수여야 한다.


컴포넌트는 부모-자식 관계에서 가장 일반적으로 함께 사용하기 위한것이다. 부모 컴포넌트는자체 템플릿에서 자식 컴포넌트를 사용할 수 있고, 이과정에서 의사소통이 필요하게 된다. 부모는 자식에게 데이터를 전달해야 할 수도 있으며, 자식은 이벤트를 부모에게 통지할 필요가 생긴다. 그러나 부모와 자식이 명확하게 정의된 인터페이스를 통해 가능한한 분리된 상태로 유지하는 것도 매우 중요하다. 이렇게 하면 각 컴포넌트의 코드를 상대적으로 격리할 수 있도록 작성하고 추로할 수 있으므로 유지 관리가 쉽고 잠재적으로 쉽게 재사용할 수 있다.


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




[Props]

모든 컴포넌트 인스턴스에는 자체 격리된 범위가 있다. 즉, 하위 컴포넌트의 템플릿에서 상위 데이터를 직접 참조할 수 없다. 데이터는 props 옵션을 사용하여 하위 컴포넌트로 전달될 수 있다.


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


HTML 속성은 대소문자를 구분하지 않으므로 문자열이 아닌 템플릿을 사용할때 camelCase prop 이름에 해당하는 kebab-case(하이픈 구분)을 사용해야 한다.

Vue.component('child', {
	// props 정의
	props: ['myMessage'],
	// 데이터와 마찬가지로 prop은 템플릿 내부에서 사용할 수 있으며
	// vm의 this.message로 사용할 수 있다.
	template: '<span>{{ myMessage }}</span>'
})
<!-- 전달하는 방식 -->
<child my-message="안녕하세요!"></child>


정규 속성을 표현식에 바인딩하는 것과 비슷하게, v-bind를 사용하여 부모의 데이터에 props를 동적으로 바인딩할 수 있다. 데이터가 상위에서 업데이트 될때마다 하위 데이터로도 전달된다.

<div>
	<input v-model="parentMsg">
	<br>
	<child :my-message="parentMsg"></child>
</div>


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



[Prop 검증]

컴포넌트가 받는 중인 prop에 대한 요구사항을 지정할 수 있다. 요구사항이 충족되지 않으면 Vue에서 경고를 내보낸다. 이 기능은 다른 사용자가 사용할 컴포넌트를 제작할 때 유용하다.


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() {
				reutnr: function() {
					return { message: 'hello' }
				}
			}
		},
		// 사용자 정의 유효성 검사 가능
		propF: {
			validator: function (value) {
				return value > 10
			}
		}
	}
})

props 검증이 실패하면 Vue는 콘솔에서 경고를 출력한다(개발빌드의 경우만). props는 컴포넌트 인스턴스가 생성되기 전에 검증되기 때문에 default 또는 validator 함수 내에서 data, computed 또는 method와 같은 인스턴스 속성을 사용할 수 없다.

<div id="app">
	<currency-input
		label="Price"
		v-model="price"
	></currency-input>
	<currency-input
		label="Shipping"
		v-model="shipping"
	></currency-input>
	<currency-input
		label="Handling"
		v-model="handling"
	></currency-input>
	<currency-input
		label="Discount"
		v-model="discount"
	></currency-input>

	<p>Total : ${{ total }}</p>
</div>
Vue.component('currency-input', {
	template: '\
		<div>\
			<label v-if="label">{{ label }}</label>\
			$\
			<input\
				ref="input"\
				:value="value"\
				@input="updateValue($event.target.value)"\
				@focus="selectAll"\
				@blur="formatValue"\
			>\
		</div>\
	',
	props: {
		value: {
			type: Number,
			default: 0
		},
		label: {
			type: String,
			default: ''
		}
	},
	mounted: function() {
		this.formatValue()
	},
	method: {
		updateValue: function (value) {
			var result = currencyValidator.parse(value, this.value)
			if (result.warning) {
				this.$refs.input.value = result.value
			}
			this.$emit('input', result.value)
		},
		formatValue: function () {
			this.$refs.input.value = currencyValidator.format(this.value)
		},
		selectAll: function (event) {
			setTimeout(function () {
				event.target.select()
			}, 0)
    	}
	}
})

new Vue({
	el: "#app",
	data: {
		price: 0,
		shipping: 0,
		handling: 0,
		discount: 0
	},
	computed: {
		total: function () {
			return ((
				this.price * 100 +
				this.shipping * 100 +
				this.handling * 100 -
				this.discount * 100
			) / 100).toFixed(2)
		}
	}
})


[컴포넌트 v-model]

기본적으로 컴포넌트의 v-model은 value를 보조 변수로 사용하고 input을 이벤트로 사용하지만 체크박스와 라디오 버튼과 같은 일부 입력 타입은 다른 목적으로 value 속성을 사용할 수도 있다. 



[비 부모-자식간 통신]

때로는 두 컴포넌트가 서로 통신할 필요가 있지만 부모/자식의 관계가 아니라면 비어있는 Vue 인스턴스를 중앙 이벤트 버스로 사용할 수 있다. 보다 복잡한 경우에는 vuex를 고려할 수 있다.

var bus = new Vue()

// 컴포넌트 A의 메소드
bus.$emit('id-selected', 1)

// 컴포넌트 B의 created 훅
bus.$on('id-selected', function(id) {
	// ...
})


[재사용 가능한 컴포넌트 제작]

컴포넌트를 작성할때 나중에 다른 곳에서 다시 사용할 것인지에 대한 여부를 미리 체크하는 것이 좋다. 일회용 컴포넌트의 경우 단단히 결합되어도 상관없지만 재사용 가능한 컴포넌트는 깨끗한 공용 인터페이스를 정의해야 하며 사용된 컨텍스트에 대한 가정을 하지 않아야 한다.


Props는 외부 환경이 데이터를 컴포넌트로 전달하도록 허용한다.

Event를 통해 컴포넌트가 외부 환경에서 사이드이펙트를 발생할수 있도록 한다.

Slot을 사용하면 외부 환경에서 추가 컨텐츠가 포함된 컴포넌트를 작성할 수 있다.




[전역 등록]

Vue.component('my-component-name', {
	// ... options ...
})

이런 컴포넌트를 전역 등록되었다고 합니다. 즉 어떤 루트 Vue 인스턴스(new Vue)에서도 사용할 수 있다.

Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })

new Vue({ el: '#app' })
<div id="app">
	<component-a></component-a>
	<component-b></component-b>
	<component-c></component-c>
</div>


이렇게 등록한 컴포넌트들은 모든 하위 컴포넌트에도 사용가능하다. 즉 위의 3개 컴포넌트들은 각각의 컴포넌트 안에서도 사용할수 있다.



[지역 등록]

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }

new Vue({
	el: '#app',
	components: {
		'component-a': ComponentA,
		'component-b': ComponentB
	}
})


컴포넌트를 일반 자바스크립트 객체로 정의할 수 있다. 그러면 사용할 컴포넌트들만 components 옵션을 통해 쓸 수 있다. components 객체의 각 속성에서 key가 커스텀 엘리먼트의 이름이 되고 value에 사용할 컴포넌트 객체를 지정한다.


지역 등록된 컴포넌트는 하위컴포넌트에서는 사용이 불가능하다. 예를 들어 ComponentA를 ComponentB에서 쓰고 싶다면 아래와 같이 하면 된다.

import ComponentA from './ComponentA.vue'

export default {
	components: {
		ComponentA
	},
	// ...
}




[모듈 시스템에서 컴포넌트를 다른 컴포넌트에게 지역적으로 등록]

이 경우에는 components 디렉토리를 만들고 각 컴포넌트들을 그 자체로 하나의 파일에 관리하는 것을 추천한다.


그러면 어떤 컴포넌트를 다른 컴포넌트에 지역적으로 등록하기 전에 사용할 컴포넌트를 가져와야 한다. 예를 들면 ComponentB.js나 componentB.vue 같은 파일에서 아래처럼 다른 컴포넌트를 가져온다.

import ComponentA from './ComponentA'
import ComponentB from './componentC'

export default {
	components: {
		ComponentA,
		ComponentC
	},
	// ...
}

이제 ComponentA와 ComponentC 모두 ComponentB의 템플릿에서 사용할 수 있다.





전역등록은 (new Vue로) 루트 Vue 인스턴스가 만들어지기 전에 반드시 이루어져야 한다.


https://github.com/chrisvfritz/vue-enterprise-boilerplate