Vue - 컴포넌트

h.Im·2024년 9월 11일
0

Vue 기초

목록 보기
20/28
post-thumbnail

컴포넌트 등록에는 전역 등록과 지역 등록이 있습니다.

전역 등록 예시 코드

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내에 지역적으로 등록된 것입니다.

컴포넌트를 만들고 전역적으로 등록하면 한 번만 등록하면 되기 때문에, 지역 등록보다 코드 수도 줄고 간편해 보일 수 있지만 전역적으로 컴포넌트를 등록하는 것은 몇 가지 단점이 있습니다.

  • Webpack 또는 Vite 같은 빌드 시스템을 사용하는 경우 컴포넌트를 전역 등록하는 것은 컴포넌트를 사용하지 않더라도 계속해서 최종 빌드에 해당 컴포넌트가 포함되는 것을 의미함. 사용자가 다운로드하는 자바스크립트 파일의 크기를 불필요하게 증가시킴.
  • 전역 등록을 계속 하게 되면 애플리케이션의 컴포넌트간 종속 관계를 확인하기 힘듦. 상위 컴포넌트, 하위 컴포넌트 구분이 힘들면 유지보수를 하기에 매우 어려워짐

컴포넌트의 네이밍 규칙에 대해서는 아래 공식 문서를 통해 숙지하고, 실무에서 정한 규칙이 있다면 그 규칙을 따르도록 해야겠습니다.
https://ko.vuejs.org/style-guide/rules-essential.html


Props

props는 컴포넌트에 등록할 수 있는 사용자 정의 속성을 의미합니다. 부모 컴포넌트로부터 자식 컴포넌트로 전달됩니다.

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 상세하게 선언하기

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>
  • validator: props로 들어오는 값에 대한 커스텀 밸리데이션(밸리데이션 통과하지 못하면 콘솔로그 워닝 노출)
  • Object 타입 props를 받는 경우, default를 위 보기와 같이 작성해야 합니다.

왜 default를 위처럼 작성해야 할까?

  • 객체와 배열은 참조형 데이터
    객체와 배열은 참조 타입이므로, 모든 인스턴스가 동일한 기본값 객체를 공유할 위험이 있습니다. 예를 들어, obj의 기본값이 {}로 설정되어 있고, 이 기본값을 여러 컴포넌트 인스턴스가 공유한다면, 한 인스턴스에서 해당 객체를 변경하면 다른 인스턴스에도 영향을 미칠 수 있습니다.
  • 독립된 인스턴스 값 제공
    default: () => { return {} }와 같이 함수로 반환하면, Vue는 각 컴포넌트 인스턴스마다 새로운 객체를 생성합니다. 이는 각 컴포넌트가 독립적인 obj 객체를 가지도록 보장하여, 다른 컴포넌트의 상태가 변경되지 않도록 합니다.

단방향 데이터 흐름

모든 props는 상위 컴포넌트에서 하위 컴포넌트로 흐르는 단방향 바인딩으로 되어 있습니다. 그럼 반대 방향으로 데이터의 전달이 필요할 경우 어떻게 해야 할까요?

Events

자식 컴포넌트에서 부모 컴포넌트로 이벤트를 전달하는 방법을 알아보겠습니다.

//// 자식 컴포넌트 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 속성은 나중에 공부해 보겠습니다.


Non-Prop 속성

Vue의 Non-Prop 속성(Non-Prop Attributes)이란, 컴포넌트에 전달된 속성 중에서 해당 컴포넌트의 props로 선언되지 않은 나머지 속성들을 의미합니다. 이 속성들은 컴포넌트에서 사용되지 않고, 자동으로 해당 컴포넌트의 최상위 DOM 엘리먼트에 적용됩니다.

Non-Prop 속성의 동작 방식

컴포넌트에 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>

0개의 댓글