티스토리 뷰
1. Element & Component Access
대부분의 경우 다른 컴포넌트의 인스턴스에 접근하거나 DOM을 직접 조작하는 것은 피해야 합니다. 그러나 때로는 다른 컴포넌트의 인스턴스에 접근하거나 DOM을 직접 조작해야 할 때가 있습니다.
1) 루트 인스턴스 접근
$root
를 사용하면 루트 인스턴스에 접근 가능합니다.
// The root Vue instance
new Vue({
data: {
foo: 1
},
computed: {
bar: function () { /* ... */ }
},
methods: {
baz: function () { /* ... */ }
}
})
위의 코드와 같이 루트 인스턴스가 있을 때,
// Get root data
this.$root.foo
// Set root data
this.$root.foo = 2
// Access root computed properties
this.$root.bar
// Call root methods
this.$root.baz()
모든 자식 컴포넌트는 위의 코드와 같이 루트 엘리먼트에 접근하여 루트 인스턴스의 값들을 사용할 수 있습니다.
2) 부모 컴포넌트 인스턴스 접근
$root
와 유사하게 $parent
를 사용하면 부모 인스턴스에 접근 가능합니다. $parent
를 사용하여 부모 컴포넌트의 데이터를 변경할 경우, 어플리케이션이 커진다면, 어느 자식 컴포넌트에서 데이터를 변경하였는지 알 수 없어 디버깅하기 더 어려워 질 수 있습니다. 하지만 몇몇의 라이브러리들을 사용할 때 부모 인스턴스를 접근하는 것이 유용할 때가 있습니다.
<google-map>
<google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
</google-map>
예를 들어, 위의 코드와 같이 Google Maps 라이브러리를 사용하여 컴포넌트를 만든다고 할 때, <google-map>
컴포넌트는 map 프로퍼티로 맵 정보를 가지고 있고, 자식 컴포넌트인 <google-map-markers>
컴포넌트가 부모 컴포넌트인 <google-map>
컴포넌트의 맵 정보를 가져와야 할 때, this.$parent.getMap
을 이용하여 맵 정보를 가져 올 수 있습니다.
<google-map>
<google-map-region v-bind:shape="cityBoundaries">
<google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
</google-map-region>
</google-map>
위의 예제와 같이 <google-map-region>
이라는 컴포넌트가 <google-map>
과 <google-map-markers>
사이에 사용될 수도 있다고 했을 때(선택적으로 사용되지 않을 수도 있음), <google-map-markers>
컴포넌트는 <google-map>
컴포넌트의 맵 정보를,
var map = this.$parent.map || this.$parent.$parent.map
위의 코드와 같이 사용할 수 있습니다.
3) 자식 컴포넌트 인스턴스, 자식 엘리먼트 접근
때로, 직접 자식 컴포넌트에 직접 접근해야 할 때가 있습니다. ref
를 사용하면 자식 컴포넌트에 접근이 가능합니다.
<base-input ref="usernameInput"></base-input>
위의 코드와 같이 부모 컴포넌트에서 자식 컴포넌트를 선언하여 사용한 후 ref
를 정의 했다면,
this.$refs.usernameInput
위의 코드와 같이 자식 컴포넌트에 직접 접근이 가능합니다.
<input ref="input">
또한 위의 코드와 같이, ref
를 사용하여 기본 엘리먼트에 접근도 가능합니다.
Vue.component('base-input', {
template: '<input ref="input">',
methods: {
// Used to focus the input from the parent
focus: function () {
this.$refs.input.focus()
}
}
})
자식 컴포넌트가 위의 코드과 같이 작성되었다면,
this.$refs.usernameInput.focus()
부모 컴포넌트에서는 위의 코드를 사용하여 자식 컴포넌트의 특정 엘리먼트에 접근도 가능해 집니다.
<ul>
<li v-for="num of len" :key="num" ref="refArray">{{ num }}</li>
</ul>
ref
는 v-for
와 같이 사용할 수 있습니다. 위의 코드와 같이 ref
와 v-for
을 함께 사용한다면,
{refArray: Array(...)}
this.$refs
의 값은 위의 코드와 같이 key는 refArray
이고 value는 Array
가 됩니다.
참고 - $refs
사용시 유의 사항
$refs
는 랜더링 됭 후 값이 채워집니다. 또한 $refs
는 반응적이지 않습니다.($refs
가 변경 되어도 watch
, computed
등으로 감지 하지 못함) 그렇기 때문에 template
에서나 computed
에서 $refs
를 사용하는 것은 피해야 합니다.
4) 의존성 주입
<google-map>
<google-map-region v-bind:shape="cityBoundaries">
<google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
</google-map-region>
</google-map>
위의 코드는 부모 컴포넌트 인스턴스 접근에서 다루었던 코드입니다. <google-map>
컴포넌트의 하위 컴포넌트에서 <google-map>
컴포넌트의 getMap
메소드를 접근하려면 $parent
를 사용했습니다. provide
와 inject
를 사용한 의존성 주입을 통해 위의 방법을 구현 할 수 있습니다.
provide: function () {
return {
getMap: this.getMap
}
}
<google-map>
컴포넌트에서는 하위 컴포넌트에서 사용할 수 있게 의존성을 주입할 메소드를 위의 코드와 깉이 정의해야 합니다. 위의 예제에서는 getMap
을 하위 컴포넌트에서 사용할 수 있도록 할 것입니다.
inject: ['getMap']
하위 컴포넌트에서는 위의 코드와 같이 의정성 주입을 한 메소드를 사용할 수 있습니다. 위의 예제는 Fiddle에서 확인 할 수 있습니다.
위의 코드와 같이 의존성 주입을 사용하면 $parent
를 사용하지 않고 모든 하위 컴포넌트에서 의존성 주입한 메소드를 사용할 수 있습니다. (위의 코드에서는 getMap
메소드) 의존성 주입을 사용하면 몇가지 장점이 있습니다.
- 부모 컴포넌트는 어떤 하위 컴포넌트에서 의존성 주입한 메소드를 사용하는지 알 필요가 없습니다.
- 하위 컴포넌트는 어떤 부모 컴포넌트에서 메소드를 의전송 주입을 하였는지 알 필요가 없습니다.
$parent
는 전체 인스턴스를 노출시키지만, 의존성 주입을 사용하면 전체 인스턴스를 노출하지 않을 수 있습니다.
2. Programmatic Event Listeners
지금까지는 $emit
으로 이벤트를 발생시키고 v-on
으로 발생시킨 이벤트를 감지하는 형태를 사용하였습니다. Vue는 또 다른 이벤트 인터페이스를 제공합니다. 이 방법을 programmatic event listener라 이야기 하도록 하겠습니다.
$on(eventName, eventHandler)
:eventName
이라는 이벤트를 감지하여 이벤트가 발생할 경우eventHandler
를 실행합니다.$once(eventName, eventHandler)
:eventName
이라는 이벤트를 한번만 감지하여eventHandler
를 실행합니다.$off(eventName, eventHandler)
:eventName
이라는 이벤트 감지를 해제하여eventHandler
를 실행합니다.
위의 이벤트 리스너들은 일반적인 경우 사용할 필요는 없지만 컴포넌트의 인스턴스에서 수동으로 이벤트를 감지해야 할 때 주로 사용됩니다. 다른 서드 파트 라이브러리를 사용할 때 유용하게 사용할 수 있습니다.
// Attach the datepicker to an input once
// it's mounted to the DOM.
mounted: function () {
// Pikaday is a 3rd-party datepicker library
this.picker = new Pikaday({
field: this.$refs.input,
format: 'YYYY-MM-DD'
})
},
// Right before the component is destroyed,
// also destroy the datepicker.
beforeDestroy: function () {
this.picker.destroy()
}
위의 코드는 2가지 잠제적인 이슈가 있습니다.
- 라이프 사이클 훅에서
picker
를 사용해야 할 경우,picker
를 컴포넌트 인스턴스에 저장해야 합니다. - 초기화 코드와, 해제 코드가 분리되어 있어, 코드 분석이 어려울 수 있습니다.
위의 2가지 이슈를 programmatic event listener를 사용하여 해결 할 수 있습니다.
mounted: function () {
var picker = new Pikaday({
field: this.$refs.input,
format: 'YYYY-MM-DD'
})
this.$once('hook:beforeDestroy', function () {
picker.destroy()
})
}
위와 같은 방법을 사용하면 pikaday
를 간편하게 여러번 사용할 수 있게 됩니다.
mounted: function () {
this.attachDatepicker('startDateInput')
this.attachDatepicker('endDateInput')
},
methods: {
attachDatepicker: function (refName) {
var picker = new Pikaday({
field: this.$refs[refName],
format: 'YYYY-MM-DD'
})
this.$once('hook:beforeDestroy', function () {
picker.destroy()
})
}
}
위의 전체 예제 코드는 Fiddle에서 확인 할 수 있습니다.
3. 순환 참조
1) 재귀 컴포넌트
컴포넌트는 자신의 template
에서 자신을 반복적으로 호출 할 수 있습니다.
name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'
주의하지 않으면 재귀 컴포넌트는 무한 루프를 만들 수도 있습니다. 그렇기 때문에 재귀 컴포넌트를 사용할 때 v-if
등을 사용하는 조건부 재귀 컴포넌트 인지 살펴보아야 합니다.
2) 컴포넌트간의 순환 참조
파일 탐색기와 같이 파일 디렉토리 드리를 작성해 보도록 하겠습니다.
<p>
<span>{{ folder.name }}</span>
<tree-folder-contents :children="folder.children"/>
</p>
위의 코드와 같이 tree-folder
라는 컴포넌트를 만듭니다.
<ul>
<li v-for="child in children">
<tree-folder v-if="child.children" :folder="child"/>
<span v-else>{{ child.name }}</span>
</li>
</ul>
tree-folder-contents
컴포넌트는 위의 코드와 같습니다.
위의 코드를 살펴보면, 두 컴포넌트는 서로가 서로의 자식 컴포넌트이지 부모 컴포넌트입니다. 여기서 역설이 발생합니다. tree-folder
컴포넌트가 랜더링 되기 위해서는 tree-folder-contents
컴포넌트가 필요하고, tree-folder-contents
컴포넌트가 랜더링 되기 위해서는 tree-folder
컴포넌트가 필요합니다.
- 전역으로 컴포넌트 선언하여 순환 참조하기
이 역설은 Vue.component
를 사용하여 전역으로 컴포넌트를 등록하면 해결 됩니다.
그러나 Webpack이나 Browserify등의 모듈러를 통해 지역으로 컴포넌트를 등록할 경우,
Failed to mount component: template or render function not defined.
위의 에러가 발생합니다. 위 에러를 해결하기 위한 방법으로 2가지가 있습니다.
- 지역으로 컴포넌트 선언하여 순환 참조하기
첫번째 방법은 tree-folder
컴포넌트를 시작점으로 잡고,
beforeCreate: function () {
this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
}
위의 코드와 같이 beforeCreate
라이프 사이클 훅을 사용하여 동적으로 컴포넌트를 등록하는 방법이 있습니다.
두번째 방법은 비동기 컴포넌트를 사용하는 방법이 있습니다.
components: {
TreeFolderContents: () => import('./tree-folder-contents.vue')
}
위의 코드와 같이 비동기 컴포넌트를 사용하여 위에서 말한 에러를 해결 할 수 있습니다.
4. Alternate Template Definitions
1) Inline Templates
하위 컴포넌트에 inline-template
속성을 사용 할 때, 컴포넌트의 내용은 그 하위 컴포넌트의 내용이 됩니다. 보다 유연한 템플릿 작성을 가능하게 합니다.
<my-component inline-template>
<div>
<p>These are compiled as the component's own template.</p>
<p>Not parent's transclusion content.</p>
</div>
</my-component>
inline-template
는 템플릿의 범위를 알아보기 힘들게 만듭니다. 그렇기 때문에 보통은 template
옵션을 사용하거나 .vue
파일의 template
엘리먼트를 사용하는 것이 좋습니다.
2) X-Templates
<script type=text/x-template">
를 사용하여 템플릿을 정의하는 방법도 있습니다.
<script type="text/x-template" id="hello-world-template">
<p>Hello hello hello</p>
</script>
Vue.component('hello-world', {
template: '#hello-world-template'
})
위의 코드와 같은 방법으로 사용할 수 있습니다.
5. Controlling Updates
Vue의 반응형 시스템의 도움으로 언제 업데이트 되는지 알 수 있습니다. 그러나 반응형 데이터가 변경되지 않았음에도 불구하고 업데이트를 해야 할 경우나, 불필요한 업데이트를 막을 수 있는 방법도 있습니다.
1) 강제 업데이트
대부분의 경우 강제 업데이트 할 필요가 없습니다. 하지만 Array 데이터 주의 사항이나 Object 데이터 주의 사항을 지키지 않았을 경우 강제 업데이트를 해야 할 때가 있습니다. 이 경우 수동으로 업데이트를 해야 하는데 $forceUpdate
를 사용하여 강제 업데이트를 진행 할 수 있습니다.
2) v-once
이름에도 알 수 있듯이 한번만 랜더링 되는 컴포넌트를 만들 때 사용됩니다. v-once
를 사용하면 업데이트 되지 않는 정적인 컴포넌트를 랜더링 할 수 있습니다. v-once를 사용하여 업데이트 하지 않아도 되는 엘리먼트들을 정적으로 랜더링 한다면 더 나은 성능을 보일 수 있습니다.
참고
'Vue.JS' 카테고리의 다른 글
[Vue.JS] 사용자 정의 디렉티브 (1) | 2019.02.07 |
---|---|
[Vue.JS] Mixins (0) | 2019.02.05 |
[Vue.JS] 컴포넌트 (고급:Dynamic & Async Components) (0) | 2019.01.29 |
[Vue.JS] 컴포넌트 (고급:slot) (0) | 2019.01.26 |
[Vue.JS] 컴포넌트 (고급:Custrom Events) (0) | 2019.01.24 |