src/components 폴더에 PageTitle.vue 파일을 추가한다
/src/components/PageTitle.vue
<template>
<h2>Page Title</h2>
</template>
src/views폴더에 NestedComponent.vue 파일을 추가한다
/src/views/NestedComponent.vue
<template>
<div>
<PageTitle />
</div>
</template>
<script>
import PageTitle from "../components/PageTitle.vue";
export default {
components: { PageTitle },
};
</script>
import
후 components에 등록한다PageTitle.vue 컴포넌트는 다른 컴포넌트에서도 헤더로 사용할 수 있는 재사용성을 지닌다
이처럼 컴포넌트는 페이지 하나 전체가 될 수도 있고, 작은 단위 요소일 수도 있다
컴포넌트 설계는 전체 애플리케이션 개발에서 매우 중요하다
/src/components/PageTitle.vue
<template>
<h2>{{ title }}</h2>
</template>
<script>
export default {
props: {
title: {
type: String,
default: "페이지 제목입니다.",
},
},
};
</script>
/src/views/NestedComponent.vue
<template>
<div>
<PageTitle title="부모 컴포넌트에서 자식 컴포넌트로 데이터 전달" />
</div>
</template>
<script>
import PageTitle from "../components/PageTitle.vue";
export default {
components: { PageTitle },
};
</script>
정적 데이터는 위와 같이 전달하면 되고, 동적 데이터는 v-bind를 사용해서 값을 전달한다
<page-title v-bind:title="title" />
<page-title :title="title" />
data() {
return {
title: "동적 페이지 타이틀"
}
}
숫자 값을 prop으로 전달하기 위해선 v-bind
를 통해서만 가능하다
<blog-post likes="42" />
<!-- 정적 -->
<blog-post :likes="42" />
<!-- 동적 -->
<blog-post :likes="post.likes" />
논리 자료형 역시 v-bind
로 전달한다
<!-- 정적 -->
<blog-post :is-published="true" />
<!-- 동적 -->
<blog-post :is-published="isShow" />
배열도 v-bind
를 이용한다
<!-- 정적 -->
<blog-post :comment-ids="[1, 2, 3]" />
<!-- 동적 -->
<blog-post :comment-ids="post.commentIds" />
객체도 v-bind
를 이용한다
<!-- 정적 -->
<blog-post :author="{name:'Veronica', age:13}" />
<!-- 동적 -->
<blog-post :author="post.author" />
객체 및 속성 역시 v-bind
를 이용한다
<!-- 동일한 두 코드 -->
<blog-post v-bind="post" />
<blog-post id:="post.id" title:="post.title"/>
data() {
return {
post: {id:1, title:'공지사항'}
}
},
자식 컴포넌트에서 props 옵션을 정할 때, 다음과 같은 방법을 통해 데이터에 대한 요구사항을 지정할 수 있다
props: {
// Number 타입 체크
propA: Number,
// 여러 타입 허용
propB: [String, Number],
// 문자형이고 부모 컴포넌트로부터 반드시 데이터가 전달되어야 함
propC: {
type: String,
required: true,
},
// 기본 값(100)을 갖는 숫자형
propD: {
type: Number,
default: 100,
},
// 기본 값을 갖는 객체 타입
propE: {
type: Object,
// 객체나 배열의 기본 값은 항상 팩토리 함수로부터 반환
default: function () {
return { message: "hello" };
},
},
// 커스텀 유효성 함수
propF: {
validator: function (value) {
// 값이 꼭 아래 세 문자열 중 하나와 일치
return ["success", "warning", "danger"].indexOf(value) !== -1;
},
},
// 기본 값을 갖는 함수
propG: {
type: Function,
// 팩토리 함수가 아닌 기본 값으로 사용되는 함수 그 자체
default: function () {
return "Default function";
},
},
},
/src/views/ChildComponent.vue
<template>
<div>
<button type="button" @click="childFunc" ref="btn">click</button>
</div>
</template>
<script>
export default {
methods: {
childFunc() {
console.log("부모 컴포넌트에서 직접 발생시킨 이벤트");
},
},
};
</script>
ref="btn"
: 자식 컴포넌트의 버튼 객체에 ref를 달아준다ref="btn"
를 지정하면 Vue 컴포넌트의 함수에서 this.$ref
를 통해 접근이 가능하다/src/views/ParentComponent.vue
<template>
<div>
<button type="button" @click="childFunc" ref="btn">click</button>
</div>
</template>
<script>
export default {
methods: {
callFromParent() {
console.log("부모 컴포넌트에서 직접 호출한 함수");
},
},
};
</script>
<child-component ref="child_component" />
: 마찬가지로 child-component에 child_component라는 ref를 달아준다this.$refs.child_component.$refs.btn.click();
: 자식의 버튼 객체에 접근해 click() 이벤트를 발생한다/src/views/ChildComponent2.vue
<template>
<div></div>
</template>
<script>
export default {
methods: {
callFromParent() {
console.log("부모 컴포넌트에서 직접 호출한 함수");
},
},
};
</script>
/src/views/ParentComponent2.vue
<template>
<div>
<child-component @send-message="sendMessage" ref="child_component" />
</div>
</template>
<script>
import ChildComponent from "./ChildComponent2.vue";
export default {
components: { ChildComponent },
mounted() {
this.$refs.child_component.callFromParent();
},
};
</script>
this.$refs.child_component.callFromParent();
: 부모 컴포넌트에서 자식 컴포넌트를 $refs
를 사용하여 접근하고, 직접 함수를 호출한다/src/views/ChildComponent3.vue
<template>
<div>
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
msg: "",
};
},
};
</script>
/src/views/ParentComponent3.vue
<template>
<div>
<child-component @send-message="sendMessage" ref="child_component" />
<button type="button" @click="changeCildData">Change Child Data</button>
</div>
</template>
<script>
import ChildComponent from "./ChildComponent3.vue";
export default {
components: { ChildComponent },
methods: {
changeCildData() {
this.$refs.child_component.msg = "부모 컴포넌트가 변경한 데이터";
},
},
};
</script>
this.$refs.child_component.msg = "부모 컴포넌트가 변경한 데이터";
: 마찬가지로 $refs
를 통해 접근한 뒤 데이터를 직접 변경한다자식 컴포넌트에서 부모 컴포넌트로 이벤트를 전달하기 위해선 $emit
을 이용한다
/src/views/ChildComponent4.vue
<template>
<div>
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
msg: "자식 컴포넌트로부터 보내는 메시지",
};
},
mounted() {
this.$emit("send-message", this.msg);
},
};
</script>
this.$emit("send-message", this.msg);
: 자식 컴포넌트가 mount 되면 부모 컴포넌트의 send-message 이벤트를 호출한다/src/views/ParentComponent4.vue
<template>
<div>
<child-component @send-message="sendMessage" ref="child_component" />
</div>
</template>
<script>
import ChildComponent from "./ChildComponent3.vue";
export default {
components: { ChildComponent },
methods: {
sendMessage(data) {
console.log(data);
},
},
};
</script>
<child-component @send-message="sendMessage" />
: 커스텀 이벤트 send-message는 자식 컴포넌트에서 $emit
으로 호출한다부모 컴포넌트에서 computed
를 이용하면 자식 컴포넌트에 정의된 데이터 옵션값의 변경사항을 항상 동기화 할 수 있다
/src/views/ChildComponent5.vue
<template>
<div>
<button type="button" @click="childFunc" ref="button">
자식 컴포넌트 데이터 변경
</button>
</div>
</template>
<script>
export default {
data() {
return {
msg: "메시지",
};
},
methods: {
childFunc() {
this.msg = "변경된 메시지";
},
},
};
</script>
/src/views/ParentComponent5.vue
<template>
<div>
<button type="button" @click="checkChild">자식 컴포넌트 데이터 조회</button>
<child-component ref="child_component" />
</div>
</template>
<script>
import ChildComponent from "./ChildComponent5.vue";
export default {
components: { ChildComponent },
computed: {
msg() {
return this.$refs.child_component.msg;
},
},
methods: {
checkChild() {
alert(this.msg);
},
},
};
</script>
$emit
을 하지 않아도 항상 최신 상태를 유지할 수 있다는 장점이 있다컴포넌트는 재활용 가능하며, 여러 컴포넌트를 import해서 사용할 수 있다고 배웠다
실무에선 굉장히 비슷한 UI와 기능을 가지고 있는데, 아주 일부만 달라 재활용을 하지 못하는 경우가 많다
slot은 컴포넌트 내에서 다른 컴포넌트를 사용할 때 쓰는 컴포넌트의 마크업을 재정의하거나 확장하는 기능으로, 재활용성을 높여준다
예를 들면, 팝업(Modal)은 굉장히 많은 화면에서 사용한다
팝업의 기본 틀에 해당하는 컴포넌트를 slot을 이용해 만들고, 개발자는 컨텐츠에 해당하는 부분만 작성한다
src/views/SlotModalLayout.vue
<template>
<div class="modal-container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
src/views/SlotUseModalLayout.vue
<modal-layout>
<template v-slot:header>
<h1>팝업 타이틀</h1>
</template>
<template v-slot:default>
<p>팝업 컨텐츠 1</p>
<p>팝업 컨텐츠 2</p>
</template>
<template v-slot:footer>
<button>닫기</button>
</template>
</modal-layout>
v-slot:[slot 이름]
디렉티브를 사용해서 동일한 이름의 slot 위치로 html 코드가 삽입된다v-slot:deafult
로 지정한다slot 사용 여부에 따라 코드는 아래와 같이 차이가 난다
slot 미사용
<template>
<div>{{ title }}</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: "페이지 제목입니다.",
},
},
};
</script>
------------------------------------------------
<PageTitle v-bind:title="slot 미사용"><PageTitle>
slot 사용
<template>
<slot></slot>
</template>
------------------------------------------------
<PageTitle>slot 사용<PageTitle>
부모에서 자식으로 데이터를 내려줄 때엔 props를 사용한다
그런데 만약 컴포넌트 계층 구조가 복잡해 부모에서 자식으로, 자식에서 그 자식으로 데이터를 전달해야하는 경우가 생긴다면, 코드가 굉장히 복잡해질 것이다
이러한 경우에 부모 컴포넌트에선 provide 옵션을, 자식 컴포넌트에선 inject 옵션을 통해 데이터를 쉽게 전달할 수 있다
예를 들어, 컴포넌트 구조가 Parent > Child > ChildChild 일 때,
Parent에서 ChildChild로 데이터를 전달하려면 Parent → Child → ChildChild 의 3단계를 거쳐 전달해야 하지만, Provide/Inject를 사용하면 한번에 바로 전달할 수 있다
src/views/ProvideInject.vue
<template>
<provide-inject-child />
</template>
<script>
import ProvideInjectChild from "./ProvideInjectChild.vue";
export default {
components: { ProvideInjectChild },
data() {
return {
items: ["A", "B"],
};
},
provide() {
return {
itemLength: this.items.length,
};
},
};
</script>
src/views/ProvideInjectChild.vue
<script>
export default {
inject: ["itemLength"],
mounted() {
console.log(this.itemLength);
},
};
</script>
inject: ["itemLength"]
: 부모 컴포넌트로부터 전달받고자 하는 데이터와 동일한 속성 이름으로 inject에 문자열 배열로 정의한다inject
를 통해서 데이터를 전달받는 자식 입장에선, 어떤 부모로부터 온 데이터인지 확인이 안된다는 단점이 있다Vue 개발시 특별한 경우가 아니면 HTML 객체에 바로 접근해서 코드를 구현할 일은 없다
하지만 어쩔수 없이 JS에서 HTML 객체에 바로 접근 해야한다면 HTML 태그에 id
대신 ref
를 사용한다
<input type="text" ref="title" />
this.$refs
를 이용해 ref 속성에 지정된 이름으로 HTML 객체에 접근이 가능하다
this.$refs.title.focus();
출처: 고승원 저, 『Vue.js 프로젝트 투입 일주일 전』, 비제이퍼블릭(2021)