컴포넌트 등록에는 전역 등록과 지역 등록이 있습니다.
import { createApp } from 'vue'
import App from './App.vue'
import AppCard from './components/AppCard.vue'
const app = createApp(App)
app.component('AppCard', AppCard)
app.mount('#app')
AppCard 컴포넌트를 전역적으로 등록하여, 모든 컴포넌트 내부에서 AppCard 컴포넌트를 사용할 수 있게됩니다.
<template>
<TheNav />
<TheView />
</template>
<script>
import TheNav from './components/TheNav.vue'
import TheView from './components/TheView.vue'
export default {
components: {
TheNav,
TheView,
},
setup() {
return {}
},
}
</script>
<style></style>
위 코드를 작성한 파일이 App.vue라고 한다면, TheNav와 TheView 컴포넌트는 App.vue내에 지역적으로 등록된 것입니다.
컴포넌트를 만들고 전역적으로 등록하면 한 번만 등록하면 되기 때문에, 지역 등록보다 코드 수도 줄고 간편해 보일 수 있지만 전역적으로 컴포넌트를 등록하는 것은 몇 가지 단점이 있습니다.
컴포넌트의 네이밍 규칙에 대해서는 아래 공식 문서를 통해 숙지하고, 실무에서 정한 규칙이 있다면 그 규칙을 따르도록 해야겠습니다.
https://ko.vuejs.org/style-guide/rules-essential.html
props는 컴포넌트에 등록할 수 있는 사용자 정의 속성을 의미합니다. 부모 컴포넌트로부터 자식 컴포넌트로 전달됩니다.
Vue 컴포넌트에는 명시적 props 선언이 필요합니다.
<template lang="">
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ title }}</h5>
<p class="card-text">
{{ contents }}
</p>
<a href="#" class="btn btn-primary">Go somewhere</a>
</div>
</div>
</template>
<script>
export default {
// title, contents라는 props를 사용할 것이라는 명시적인 선언
props: ['title', 'contents'],
setup() {
return {}
},
}
</script>
<style lang=""></style>
title과 contents라는 props를 명시적으로 선언하고, template 내부에서 그 값을 사용하는 모습을 볼 수 있습니다. AppCard.vue 컴포넌트에 title과 contents를 전달하는 방식을 살펴보겠습니다.
<div class="col col-4">
<!-- 하드코딩해서 전달한 방식 -->
<AppCard title="제목1" contents="내용1" />
</div>
<div class="col col-4">
<!-- reactive 객체를 전달한 방식 -->
<AppCard :title="post.title" :contents="post.contents" />
</div>
<!-- v-for를 이용해 반복 출력하는 방식 -->
<div v-for="post in posts" :key="post.id" class="col col-4">
<AppCard :title="post.title" :contents="post.contents" />
</div>
AppCard를 사용하는 곳에서 위 코드와 같이 props를 전달할 수 있습니다(이전에 공부했던 대로, 아래 두 예제는 v-bind를 통해 전달했기 때문에 :title로 전달한 모습입니다).
props의 동작 방식을 간단하게 살펴보기 위해 아주 간단히 문자열 배열로 선언한 예제를 보았지만, vue 스타일가이드에서는 props를 상세하게 선언하는 것을 권장하고 있습니다.
<template lang="">
...
</template>
<script>
export default {
props: {
type: {
type: String,
default: 'news',
},
title: {
type: String,
default: 'news',
validator: (value) => {
return ['news', 'notice'].includes(value)
},
},
contents: {
type: String,
required: true,
},
isLike: {
type: Boolean,
default: false,
},
obj: {
type: Object,
default: () => {
return {}
},
},
},
setup() {
return {}
},
}
</script>
<style lang=""></style>
모든 props는 상위 컴포넌트에서 하위 컴포넌트로 흐르는 단방향 바인딩으로 되어 있습니다. 그럼 반대 방향으로 데이터의 전달이 필요할 경우 어떻게 해야 할까요?
자식 컴포넌트에서 부모 컴포넌트로 이벤트를 전달하는 방법을 알아보겠습니다.
//// 자식 컴포넌트 PostCreate.vue
<template lang="">
<button
class="btn btn-primary"
@click="$emit('createPost', 1, 2, 3, '김길동')"
>
button
</button>
</template>
<script>
export default {
setup(props, context) {
return {}
},
}
</script>
<style lang=""></style>
//// 부모 컴포넌트
...
<PostCreate @create-post="createPost" />
...
const createPost = (a, b, c, d) => {
console.log('createPost', a, b, c, d)
}
...
$emit을 통해 부모 컴포넌트에 createPost 이벤트 전달, 부모 컴포넌트에서 createPost 함수를 정의하여 console출력하는 과정입니다. 자식 컴포넌트에서 매개변수 전달도 위 코드 예시처럼 가능합니다.
또 다른 예시로 setup 함수의 두 번째 인자인 context를 이용하여 emit하는 방식도 있습니다. 동작은 동일합니다.
<template lang="">
<button class="btn btn-primary" @click="createPost">button</button>
</template>
<script>
export default {
setup(props, context) {
const createPost = () => {
context.emit('createPost', 1, 2, 3, '김길동')
}
return { createPost }
},
}
</script>
<style lang=""></style>
객체문법을 사용하여 emit할 이벤트를 선언하는 경우 validation 로직을 추가할 수 있습니다. validation이 필요하지 않다면 null로 설정하면 됩니다.
emits: {
// validation이 없는 경우
someEvent: null,
// validation이 있는 경우
someSubmit: (result) => {
if (email && password) {
return true
} else {
console.warn('email, pw 미존재')
return false
}
}
},
$emit()으로도 이벤트가 부모 컴포넌트로 잘 넘겨지는 것을 확인하였는데, emits를 작성하는 것은 필수일까요? 그건 아닙니다. 그러나 컴포넌트가 작동하는 방식을 더 잘 문서화하기 위해 모든 이벤트를 정의하는 것이 좋다고 vue 공식 문서에 기재되어 있습니다. 또한, fallthrough 속성에서 이벤트를 제외시키기 위해서는 이벤트를 선언해야 한다. fallthrough 속성은 나중에 공부해 보겠습니다.
Vue의 Non-Prop 속성(Non-Prop Attributes)이란, 컴포넌트에 전달된 속성 중에서 해당 컴포넌트의 props로 선언되지 않은 나머지 속성들을 의미합니다. 이 속성들은 컴포넌트에서 사용되지 않고, 자동으로 해당 컴포넌트의 최상위 DOM 엘리먼트에 적용됩니다.
컴포넌트에 props로 선언된 속성 외에도 다양한 HTML 속성들이 전달될 수 있습니다. 이때, props로 선언되지 않은 속성들은 컴포넌트 내부에서 접근하지 않지만, 컴포넌트의 루트 요소에 자동으로 적용됩니다. 이를 통해 HTML 속성들(id, class, style 등)을 쉽게 전달할 수 있습니다.
<template>
<button>Click me</button>
</template>
<script>
export default {
props: {
type: String
}
}
</script>
<!-- 컴포넌트 사용 -->
<MyButton id="submit-btn" class="btn-primary" disabled />
위 코드에서 type을 props로 선언하였기 때문에 type을 전달하면 props로 동작하여 컴포넌트 내부에서 접근 가능합니다. 하지만 id, class는 props로 선언하지 않았기 때문에 아래와 같이 DOM 요소에 적용됩니다.
<button id="submit-btn" class="btn-primary" disabled>Click me</button>