컴포넌트 심화
컴포넌트 in 컴포넌트
부모와 자식
컴포넌트에서 다른 컴포넌트 사용하는 방법
- 사용할 컴포넌트를 import한다
- 현재 컴포넌트의 템플릿에서 사용할 컴포넌트를 components에 등록한다
- 지역등록 형태
예시1 2 3 4 5 6 7 8 9
| import ComponentA from './ComponentA' import ComponentC from './ComponentC'
export default { components: { ComponentA, ComponentC } }
|
예제)
PageTitle.vue1 2 3 4 5 6 7 8
| <template> <h2>Page Title</h2> </template> <script> export default { name: "PageTitle" } </script>
|
NestedComponent.vue1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <div> <page-title/> </div> </template>
<script> import PageTitle from "../components/PageTitle"; export default { name: "NestedComponent", components: {PageTitle} } </script>
|
summary
- 개발자 각각 자기가 맡은 화면을 구성하면 개발자마 다른 화면으로 통일성이 없어짐
- 위의 경우 PageTitle 컴포넌트를 개발자들이 import해서 사용한다면 이후 해당 변경 필요시
해당 컴포넌트만 수정하면 됨
- 컴포넌트는 단위도 되며 페이지 하나의 전체가 될 수도 있음 - 설계가 중요
부모 → 자식 : Props
예제수정)
PageTitle.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <h2>{{title}}</h2> </template>
<script> export default { name: "PageTitle", props: { title: { type: String, default: "페이지 제목 기본값" } } } </script>
|
NestedComponent.vue1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <div> <page-title title="부모가 주는 데이터"/> </div> </template>
<script> import PageTitle from "../components/PageTitle"; export default { name: "NestedComponent", components: {PageTitle} } </script>
|
부모 → 자식 데이터 전달
- 속성 정의
- 정의된 속성과 동일한 이름의 속성명을 자식 컴포넌트의 props에 정의
동적 props 전달
v-bind를 사용한 동적 props전달1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <div> <page-title :title="title" /> </div> </template>
<script> import PageTitle from "../components/PageTitle"; export default { name: "NestedComponent", components: {PageTitle}, data() { return { title: '동적 페이지 타이틀' } } } </script>
|
숫자형 전달
- 숫자값을 props로 전달하기 위해선 무조건 v-bind를 사용해야 한다
1 2 3 4 5 6 7 8
| <blog-post likes="42"/>
<blog-post :likes="42"/>
<blog-post likes="post.likes"/>
|
논리형(Boolean) 전달
- 숫자형과 마찬가지로 v-bind를 사용하지 않으면 문자열로 전달된다
1 2 3 4 5
| <blog-post :is-published="true"/>
<blog-post :is-published="isShow"/>
|
그밖에
- 배열(Array) 역시 마찬가지로 v-bind를 사용하지 않으면 문자열로 전달 된다
- 객체(Object) 역시 마찬가지로 v-bind를 사용하지 않으면 문자열로 전달 된다
- 객체의 속성(attribute) 역시 마찬가지로 v-bind를 사용하지 않으면 문자열로 전달 된다
유효성 검사
props 정의시 다음과 같은 옵션을 정할 수 있다
- 전달 받는 데이터 타입(type)
- 기본 값 (default)
- 필수값 여부 (required)
- 유효성 검사 함수 (validator)
1 2 3 4 5
| props: { validator: function(value) { return ~~~; } }
|
부모 컴포넌트에서 자식 컴포넌트의 이벤트 직접 발생 시키기
ChildComponent.vue1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <button type="button" v-on:click="childFunc" ref="btn">click</button> </template> <script> export default { name: "ChildComponent", methods: { childFunc() { console.log('부모컴포넌트에서 직접 발생시킨 이벤트'); } } } </script>
|
ParentComponent.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <child-component v-on:send-message="sendMessage" ref="child_component"/> </template>
<script> import ChildComponent from "./ChildComponent";
export default { name: "ParentComponent", components: {ChildComponent}, mounted() { this.$refs.child_component.$refs.btn.click(); } } </script>
|
- 부모에서 자식 컴포넌트에 ref를 지정하여 $refs로 접근 가능
부모 컴포넌트에서 자식 컴포넌트의 함수 직접 호출하기
ChildComponent2.vue1 2 3 4 5 6 7 8 9 10
| <script> export default { name: "ChildComponent2", methods: { callFromParent() { console.log('부모 컴포넌트에서 직접 호출한 함수'); } } } </script>
|
ParentComponent2.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <child-component2 v-on:send-message="sendMessage" ref="child_component"/> </template>
<script> import ChildComponent2 from "./ChildComponent2"; export default { name: "ParentComponent2", components: {ChildComponent2}, mounted() { this.$refs.child_component.callFromParent() } } </script>
|
- 부모에서 자식 컴포넌트를 $refs를 사용해서 접근하면 자식 컴포넌트의 모든 함수 호출 가능
부모 컴포넌트에서 자식 컴포넌트의 데이터 옵션 값 직접 변경
ChildComponent3.vue1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <h1>{{msg}}</h1> </template> <script> export default { name: "ChildComponent3", data() { return { msg: '원래 msg data' }; } } </script>
|
ParentComponent3.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <child-component3 v-on:send-message="sendMessage" ref="child_component"/> <button type="button" v-on:click="changeChildData">Change Child Data</button> </template> <script> import ChildComponent3 from "./ChildComponent3"; export default { name: "ParentComponent3", components: {ChildComponent3}, methods: { changeChildData() { this.$refs.child_component.msg = '부모가 컴포넌트가 변경한 데이터'; } } } </script>
|
자식 → 부모 컴포넌트로 이벤트 전달
- data는 부모에서 자식으로만 가능
- 자식에서 부모로 전달하려면 이벤트를 이용해서 이벤트의 파라미터로 데이터 전달이 가능
$emit
을 사용한다
ChildComponent4.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <script> export default { name: "ChildComponent4", data() { return { msg: '자식 컴포넌트 data의 msg' }; }, mounted() { this.$emit('send-message', this.msg); } } </script>
|
ParentComponent4.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
<template> <ChildComponent4 v-on:send-message="sendMessage"/> </template>
<script> import ChildComponent4 from "./ChildComponent4"; export default { name: "ParentComponent4", components: {ChildComponent4}, methods: { sendMessage(value) { console.log(value); } } } </script>
|
부모에서 자식 컴포넌트의 데이터 옵션값 sync
- 부모에서 computed를 이용하면 자식 컴포넌트에 정의된 데이터 옵션 값의 변경사항을 항상 동기화 가능
$emit
을 통해 변경된 데이터를 전송하지 않아도 변경된 데이터값을 항상 확인 가능하므로 유용할 것 같음
ChildComponent5.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <button type="button" @click="childFunc" ref="child_btn">자식 컴포넌트 데이터 변경</button> </template>
<script> export default { name: "ChildComponent5", data() { return { msg: '원래 메시지' }; }, methods: { childFunc() { this.msg = '변경된 메시지'; console.log('데이터 변경'); } } } </script>
|
ParentComponent5.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <button type="button" v-on:click="checkChild">자식 컴포넌트 데이터 조회</button> <child-component5 ref="child_component"/> </template>
<script> import ChildComponent5 from "./ChildComponent5"; export default { name: "ParentComponent5", components: {ChildComponent5}, computed: { msg(){ return this.$refs.child_component.msg; } }, methods: { checkChild() { alert(this.msg) } } } </script>
|
- computed에 $refs를 참조한 자식 컴포넌트의 msg가 오리지널 데이터
- computed는 참조하고 있는 데이터 변경사항을 바로 감지해서 반영
Slot
상황
- 지금까지의 컴포넌트는 재활용 가능하며, 여러개를 자식으로 import해서 사용 가능했음
- 프로젝트시 맞딱트리는 곤란한 상황
- 다들 굉장히 비슷한 UI와 기능을 가지고 있으면서 아주 일부만 전부 각각 다른 경우
- 예) 모달 팝업
- 모달 팝업은 제목이 있는 header , 내용이 있는 바디, 하단에 버튼이 있는 footer로 이루어짐
- 좋은 어플리케이션은 단순한 팝업이라도 모든 팝업창의 디자인을 동일하게 유지 → 동일한 UX를 줄 수 있도록
- 여러 개발자가 각각 개발하면 일관성이 없어짐
SLOT
- 슬롯 : 컴포넌트 내에서 다른 컴포넌트를 사용시 컴포넌트의 마크업을 재정의/확장 가능
- 기본 틀을에 해당하는 컴포넌트를 Slot을 이용해 만들고 개발자들에게 제공
- 개발자들은 자신의 화면을 만들면서 해당 컴포넌트의 컨텐츠만 작성하면 됨
modal-layout 컴포넌트1 2 3 4 5 6 7 8 9 10 11
| <div class="modal-container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>
|
- ‘Named Slots’ : name을 지정해서 사용하는 Slot
- 기존 부모→자식에게 Props로 데이터 전달하는 예제도 Slot을 처리하면 훨씬 간단히 처리 가능
- 단순값을 위해 props를 정의할 필요가 없음
- 부모에서 자식 컴포넌트로 props데이터를 전달할 필요도 없음
- 코드가 훨씬 간결/직관적
- v-slot:대신에 단축어로 # 사용 가능
실무팁
- 프로젝트 개발 초기에 개발팀은 어플리케이션 전체에서 사용될 slot 기반 컴포넌트를 구현해서 개발자들에게 제공해야 한다
- 팝업, 페이지 타이틀등 다수 컴포넌트에서 공통으로 사용해야 하는 공통 UI 요소를 slot 기반의 컴포넌트로 만들어서 제공 필요
- 그러면 전체 개발 생산성 향상 및 통일된 디자인을 통한 UX향상 가능
- 프로젝트 초기에 이루어져야 한다
- 한번 개발된 slot 기반 컴포넌트는 다른 어플리케이션에서도 사용 가능 → 개발팀의 자산으로 지속적 관리 필요
8.3 Provide / Inject
- 부모 → 자식 : props를 이용해 데이터 전달
- 컴포넌트 계층 구조 복잡하면?
- 예) 부모에서 자식, 그리고 그 자식의 자식 컴포넌트로 데이터 전달
- 이 경우 props를 통한 데이터 전달로 구현하면 굉장히 복잡한 코드 양산
- Provide/Inject : 계층 구조 상관없이 부모는 Provide, 자식은 inject 옵션을 통해 데이터를 쉽게 전달 가능
다음과 같은 컴포넌트 구조에서 ParentComponent 에서 ChildChildComponent2로 데이터를 전달한다고 했을 때
1 2 3 4 5 6
| Root └─ ParentComponent ├─ ChildComponent1 └─ ChildComponent2 ├─ ChildChildComponent1 └─ ChildChildComponent2
|
props 사용 한다면? : ParentComponent → ChildComponent2 → ChildChildComponent2 이렇게 3단계를 거쳐서 전달해야한다
하지만 provide/inject를 사용한다면?
ProvideInject.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <template> <ProvideInjectChild/> </template>
<script> import ProvideInjectChild from "./ProvideInjectChild"; export default { name: "ProvideInject", components: {ProvideInjectChild}, data() { return { items: ['A', 'B'] }; }, provide() { return { itemLength: this.items.length }; } } </script>
<style scoped>
</style>
|
ProvideInjectChild.vue1 2 3 4 5 6 7 8 9 10 11 12
| <template> <div></div> </template> <script> export default { name: "ProvideInjectChild", inject: ['itemLength'], mounted() { console.log(this.itemLength); } } </script>
|
재정리
- 부모에서 자식 컴포넌트로 전달하고자 하는 데이터를 provide에 정의
- 자식에서 부모로부터 전달받고자 하는 데이터와 동일한 속성 이름으로 inject에 문자열 배열로 정의
장단점
- 장점 : 아무리 컴포넌트 계층구조가 복잡해도 원하는 자식에게 데이터를 한 번에 전달 가능
- 단점 : 자식은 전달받은 데이터가 어떤 부모로부터 전달되었는지 확인이 불가능하다.
Template refs
- HTML객체에 바로 접근해서 코드를 구현하는 경우는 Vue개발시 거의 없음
- 만약 어쩔수 없이 자바스크립트에서 HTML객체에 바로 접근해야한다면?
- HTML태그에 id 대신 ref를 사용한다
this.$refs
를 이용해 ref속성에 지정된 이름으로 HTML 객체에 접근 가능
Related POST