Welcome :っ)

Devlog/Front-End

[Vue.js] 컴포넌트 심화 학습_ch.8

lazy.won 2022. 3. 7. 19:34
728x90
반응형

 

 

 

 

컴포넌트 안에 다른 컴포넌트 사용하기

□ 부모 컴포넌트와 자식 컴포넌트

다음과 같이 컴포넌트를 하나 구현한다. src/components 폴더에 PageTitle.vue 파일을 생성한다.

<template>
    <h2>Page Title</h2>
</template>

src/views 폴더에 NestedComponent.vue 파일을 생성한다. 컴포넌트에서 다른 컴포넌트를 사용하는 방법은 사용할 컴포넌트를 import 한 후 현재 컴포넌트의 템플릿에서 사용할 컴포넌트를 components에 등록하면 된다. 

<template>
    <div>
        <PageTitle />
    </div>
</template>
<script>
import PageTitle from '../components/PageTitle'

export default {
    components: { //현재 컴포넌트에서 사용할 컴포넌트 등록
        PageTitle
    }
}
</script>

 

개발하면서, 혹은 애플리케이션이 서비스 되면서 각 페이지 타이들의 색상이나 레이아웃 변경 등 디자인 변경이 필요한 경우, PageTitle 컴포넌트만 수정하면 모든 화면에 반영되기 때문에 훨씬 효율적으로 관리할 수 있다.

 

 

□ 부모 컴포넌트에서 자식 컴포넌트로 데이터 전달하기 : Props

PageTitle 컴포넌트를 호출할 때 각 페이지의 실제 타이틀을 데이터로 전달하고, PageTitle 컴포넌트에서는 이를 받아서 출력하도록 변경해 보자. 

 

PageTitle.vue 파일을 다음과 같이 수정한다. 

<template>
    <h2>{{title}}</h2>
</template>
<script>
    export default {
        props: {
            title: {
                type: String,
                default: "페이지 제목입니다."
            }
        }
    }
</script>

props에는 부모 컴포넌트로 전달받은 데이터가 저장 된다.  NestedComponent.vue 파일 내의 PageTitle 컴포넌트 속성으로 다음과 같이 추가한다. 

<PageTitle title="부모 컴포넌트에서 자식 컴포넌트로 데이터 전달" />

이처럼 부모 컴포넌트에서 자식 컴포넌트로 데이터 전달 시 속성을 정의하고, 정의된 속성과 동일한 이름의 속성명을 자식 컴포넌트의 props에 정의해서 사용하면 된다. 

 

 

■ 정적/동적 Prop 전달

자식 컴포넌트인 PageTtile.vue로 title="컴포넌트 사용 예제 페이지" 정적 값을 전달하는 것을 확인했다. 

 

v-bind나 약어인 : 문자를 사용해서 prop에 동적인 값을 전달할 수 있다.

<page-title: title="title" />

data() {
	return {
    	title: '동적 페이지 타이틀'
    };
}

■ 숫자형(Number) 전달

숫자 값을 prop으로 전달하기 위해서는 v-bind를 통해서만 가능하다. v-bind를 사용하지 않은 경우는 숫자가 아닌 문자로 전달이 된다. 숫자 값으로 전달하기 위해서는 v-bind를 사용해서 정적으로 전달하거나 동적으로 전달해야 한다. 

<!-- 42는 정적이지만, v-bind를 사용함으로써 전달되는 데이터가 자바스크립트 표현식이 된다. -->
<blog-post :likes="42" />

<!-- 변수 값에 동적으로 할당한다. -->
<blog-post :likes="post.likes" />

■ 논리 자료형(Boolean) 전달

논리 자료형도 v-bind를 사용하지 않으면 문자열로 전달되기 때문에, v-bind를 사용해야 한다. 

<!-- true는 정적이지만, v-bind를 사용함으로써 전달되는 데이터가 자바스크립트 표현식이 된다. -->
<blog-post :is-published="true" />

<!-- 변수 값에 동적으로 할당한다. -->
<blog-post :is-published="isShow" />

■ 배열(Array) 전달

배열도 v-bind를 사용하지 않으면 문자열로 전달되기 때문에, v-bind를 사용해야 한다. 

<!-- 배열이 정적이지만, v-bind를 사용함으로써 전달되는 데이터가 자바스크립트 표현식이 된다. -->
<blog-post :comment-ids="[234, 266, 273]" />

<!-- 변수 값에 동적으로 할당한다. -->
<blog-post :comment-ids="post.commentIds" />

■ 객체(Object) 전달

객체도 v-bind를 사용하지 않으면 문자열로 전달되기 때문에, v-bind를 사용해야 한다. 

<!-- 객체가 정적이지만, v-bind를 사용함으로써 전달되는 데이터가 자바스크립트 표현식이 된다. -->
<blog-post :author="{name: 'Veronica', 'Veridian Dynamics'}" />

<!-- 변수 값에 동적으로 할당한다. -->
<blog-post :author="post.author" />

■ 객체(Object)의 속성 전달

다음 두 개의 코드는 동일하다.

<blog-post v-bind="post" />

<blog-post :id="post.id" :title="post.title" />
data() {
	return {
    	post: {id:1, title:'Vue 3 프로젝트'}
    };
}

■ Prop 유효성 검사

자식 컴포넌트에서 props 옵션을 정의할 때, 전달받는 데이터 타입, 기본 값(default), 필수 여부(required) 그리고 유효성 검사 함수(validator)인 함수를 통해서 유효성을 검사할 수 있다. 

props: {
	// 기본 타입 체크 ('null'과 'undefined'는 모든 타입 유효성 검사를 통과한다.)
    propA: Number, //Number 타입 체크
    propB: [String, Number], //여러 타입 허용
    propC: { // 문자형이고 부모 컴포넌트로부터 반드시 데이터가 필수로 전달되어야 함.
    	type: String,
        required: true
    },
    propD: { //기본 값(100)을 갖는 숫자형
    	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>
    <button type="button" @click="childFunc" ref="btn">click</button>
</template>
<script>
    export default {
        methods: {
            childFunc() {
                console.log('부모 컴포넌트에서 직접 발생시킨 이벤트');
            }
        },
    }
</script>

자식 컴포넌트에 버튼 객체에 ref="btn"로 접근할 수 있도록 작성했다.

HTML 태그에 ref="id"를 지정하면 Vue 컴포넌트의 함수에서 this.$refs를 통해 접근이 가능하게 된다. ref 속성은 HTML 태그에서 사용되는 id와 비슷한 기능을 한다고 생각하면 된다. ref는 유일한 키 값을 사용해야 한다.

 

src/views/ParentComponent.vue

<template>
    <child-component @send-message="sendMessage" ref="child_component" />
</template>
<script>
import ChildComponent from './ChildComponent';

export default {
    components: {ChildComponent},
    mounted() {
        this.$refs.child_component.$refs.btn.click();
    }
}
</script>

부모 컴포넌트에서 자식 컴포넌트인 child-component에 ref="child_component"를 지정하여, $ref로 접근할 수 있도록 했다. 

이렇게 설정하면 부모 컴포넌트에서 자식 컴포넌트 안에 정의된 HTML 객체에 대한 접근이 가능해지고, 자식 컴포넌트의 버튼 객체에 정의한 ref="btn" 이름으로 버튼 객체에 접근해서 click() 이벤트를 발생시킬 수 있다. 

 

 

□ 부모 컴포넌트에서 자식 컴포넌트의 함수 직접 호출하기

부모 컴포넌트에서 자식 컴포넌트에 정의된 함수를 직접 호출해 보겠다.

src/views/ChildComponent.vue

methods: {
    callFromParent() {
        console.log('부모 컴포넌트에서 직접 호출한 함수');
    }
}

src/views/ParentComponent.vue

<template>
    <child-component @send-message="sendMessage" ref="child_component" />
</template>
<script>
import ChildComponent from './ChildComponent';

export default {
    components: {ChildComponent},
    mounted() {
        this.$refs.child_component.callFromParent();
    }
}
</script>

부모 컴포넌트에서는 자식 컴포넌트를 $refs를 사용하여 접근하게 되면 자식 컴포넌트 내에 정의된 모든 함수를 호출할 수 있다.

 

 

□ 부모 컴포넌트에서 자식 컴포넌트의 데이터 옵션 값 직접 변경하기

부모 컴포넌트에서는 자식 컴포넌트의 데이터 옵션 값을 직접 변경할 수 있다.

 

src/views/ChildComponent.vue

자식 컴포넌트에는 데이터 옵션에 msg가 정의되었다. 

<template>
    <h1>{{msg}}</h1>
</template>
<script>
    export default {
        data() {
            return {
                msg: ''
            };
        }
    }
</script>

src/views/ParentComponent.vue

$refs를 통해 자식 컴포넌트에 접근하고 나면 자식 컴포넌트에 정의된 데이터 옵션을 직접 변경할 수 있다. 

<template>
    <div>
        <child-component @send-message="sendMessage" ref="child_component" />
        <button type="button" @click="changeChildData">Change Child Data</button>
    </div>
</template>
<script>
import ChildComponent from './ChildComponent';

export default {
    components: {ChildComponent},
    methods: {
        changeChildData() {
            this.$refs.child_component.msg = '부모 컴포넌트가 변경한 데이터';
        }
    }
}
</script>

 

 

□ 자식 컴포넌트에서 부모 컴포넌트로 이벤트/데이터 전달하기 (커스텀 이벤트)

자식 컴포넌트에서 부모 컴포넌트로 이벤트 전달하기 위해서는 $emit를 사용한다.

 

src/views/ChildComponent.vue

자식 컴포넌트가 mounted되면 $emit을 통해 부모 컴포넌트의 send-message 이벤트를 호출한다. 이때 msg 데이터를 파라미터로 전송한다. 

<template>
    <h1>{{msg}}</h1>
</template>
<script>
    export default {
        data() {
            return {
                msg: '자식 컴포넌트로부터 보내는 메시지'
            };
        },
        mounted() {
            this.$emit('send-message', this.msg)
        }
    }
</script>

src/views/ParentComponent.vue

부모 컴포넌트에서 자식 컴포넌트인 ChildComponent를 import 하고, 커스텀 이벤트(@send-message)를 정의한다. 커스텀 이벤트인 send-message는 자식 컴포넌트에서 $emit으로 호출하게 된다. 이때 부모 컴포넌트에 정의된 sendMessage() 함수가 실행되고 자식 컴포넌트로부터 전달받은 데이터를 부모 컴포넌트에서 사용할 수 있다. 

<template>
    <child-component @send-message="sendMessage" />
</template>
<script>
import ChildComponent from './ChildComponent';

export default {
    components: {ChildComponent},
    methods: {
        sendMessage(data) {
            console.log(data);
        }
    }
}
</script>

 

 

□ 부모 컴포넌트에서 자식 컴포넌트의 데이터 옵션 값 동기화하기

부모 컴포넌트에서 computed를 이용하면 자식 컴포넌트에 정의된 데이터 옵션 값의 변경사항을 항상 동기화시킬 수 있다. 

 

src/views/ChildComponent.vue

자식 컴포넌트에는 데이터 옵션에 msg가 정의되어 있다.

<template>
    <h1>{{msg}}</h1>
</template>
<script>
    export default {
        data() {
            return {
                msg: '자식 컴포넌트로부터 보내는 메시지'
            };
        },
        mounted() {
            this.$emit('send-message', this.msg)
        }
    }
</script>

src/views/ParentComponent.vue

부모 컴포넌트에는 computed 옵션을 사용해서, 자식 컴포넌트의 msg 값을 감지하도록 했다. computed는 참조되고 있는 데이터의 변경사항을 바로 감지하여 반영할 수 있다. 

computed를 사용하면 자식 컴포넌트의 데이터가 변경될 때마다 $emit을 통해 변경된 데이터를 전송하지 않아도 변경된 데이터 값을 항상 확인할 수 있다. 

<template>
    <div>
        <button type="button" @click="checkChild">자식 컴포넌트 데이터 조회</button>
        <child-component ref="child_component" />
    </div>
</template>
<script>
import ChildComponent from './ChildComponent';

export default {
    components: {ChildComponent},
    computed: {
        msg() {
            return this.$refs.child_component.msg;
        }
    },
    methods: {
        checkChild() {
            alert(this.msg);
        }
    }
}
</script>

 

 

Slot

slot은 컴포넌트 내에서 다른 컴포넌트를 사용할 때 쓰는 컴포넌트의 마크업을 재정의하거나 확장하는 기능이다. 컴포넌트의 재활용성을 높여주는 기능이다.

 

예를 들어서, 팝업의 기본 틀에 해당하는 컴포넌트를 slot을 이용해서 만들고, 개발자에게 제공하면 개발자는 팝업 디자인의 통일성을 유지하면서 컨텐츠에 해당하는 부분만 작성하면 된다. 

 

다음은 팝업 기본 틀에 해당하는 modal-layout 컴포넌트이다. 

<div class="modal-container">
    <header>
        <slot name="header"></slot>
    </header>
    <main>
        <slot></slot>
    </main>
    <footer>
        <slot name="footer"></slot>
    </footer>
</div>

이렇게 slot에 name을 지정해서 사용하는 slot을 'Named Slots'라고 한다. 이 컴포넌트는 정해진 html 구조를 갖게 된다. 이렇게 작성된 컴포넌트를 제공하고, 개발자는 각 slot에 해당하는 코드만 작성하면 되기 때문에 어떤 개발자가 구현하더라도 동일한 디자인의 팝업을 만들 수 있게 된다. 

 

slot을 사용하는 컴포넌트에서는 삽입한 컴포넌트 안에서 다음과 같이 template 태그를 사용하여 html 코드를 작성할 수 있다. 이때 v-slot:(slot 이름) 디렉티브를 사용해서 동일한 이름의 slot 위치로 html 코드가 삽입된다. Name이 없는 slot은  v=slot:default로 지정한다.

<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 type="button">닫기</button>
    </template>
</modal-layout>

이렇게 적용된 결과는 다음과 같다.

<div class="modal-container">
    <header>
        <h1>팝업 타이틀</h1>
    </header>
    <main>
        <p>팝업 컨텐츠 1</p>
        <p>팝업 컨텐츠 2</p>
    </main>
    <footer>
        <button type="button">닫기</button>
    </footer>
</div>

컴포넌트 내에 여러 영역에 slot을 적용할 때는 name을 이용해서 적용하고, 하나의 영역에만 적용할 때는 slot에 name을 사용하지 않아도 된다. 

 

부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 예제 코드로 PageTitle.vue를 만들었다. 

<template>
    <h2>{{title}}</h2>
</template>
<script>
    export default {
        props: {
            title: {
                type: String,
                default: "페이지 제목입니다."
            }
        }
    }
</script>

이 코드를 slot을 이용하면 다음과 같이 바뀐다.

<template>
    <h2><slot></slot></h2>
</template>
<PageTitle>컴포넌트 사용 예제 페이지</PageTitle>

단순히 페이지 타이틀을 만들기 위해 props를 정의할 필요도 없고, 부모에서 자식 컴포넌트로 props 데이터를 전달할 필요도 없게 된다. 코드가 훨씬 간결하고 직관적으로 바뀌었다. 

 

<modal-layout>
    <template #header>
        <h1>팝업 타이틀</h1>
    </template>
    <template #default>
        <p>팝업 컨텐츠 1</p>
        <p>팝업 컨텐츠 2</p>
    </template>
    <template #footer>
        <button type="button">닫기</button>
    </template>
</modal-layout>

=> v-slot: 대신 단축어로 #을 사용 할 수 있다.

 

 

Provide/Inject

만약 컴포넌트 계층 구조가 복잡하게 얽혀 있어 부모 컴포넌트로부터 자식 컴포넌트, 그리고 그 자식 컴포넌트의 자식 컴포넌트로 데이터를 전달하는 경우가 발생하면 props를 통해 데이터를 전달하는 것은 굉장히 복잡한 코드를 양산하게 된다.

 

이 경우 사용할 수 있는 것이 Provide/Inject 이다.

 

컴포넌트 계층 구조가 아무리 복잡하더라도 부모 컴포넌트에서는 provide 옵션을, 자식 컴포넌트에서는 inject 옵션을 통해 데이터를 쉽게 전달할 수 있다. 

 

src/views/ProvideInject.vue

<template>
    <ProvideInjectChild />
</template>
<script>
    import ProvideInjectChild from './ProvideInjectChild'

    export default {
        components: {ProvideInjectChild},
        data() {
            return {
                items: ['A', 'B']
            };
        },
        provide() {
            return {
                itemLength: this.items.length
            };
        }
    }
</script>

provide 함수를 통해 배열 items의 length를 반환한다. (자식 컴포넌트로 전달하고자 하는 데이터를 provide에 정의한다. )

 

ProvideInjectChild.vue

<template>
    <div></div>
</template>
<script>
    export default {
        inject: ['itemLength'],
        mounted() {
            console.log(this.itemLength);
        }
    }
</script>

부모 컴포넌트로부터 전달받고자 하는 데이터와 동일한 속성 이름으로 inject에 문자열 배열로 정의한다. 

 

이렇게 Provide/Inject를 이용하면 아무리 컴포넌트 계층 구조가 복잡하더라도 원하는 자식 컴포넌트로 데이터를 한 번에 전달할 수 있다. 

하지만, inject를 통해 데이터를 전달받는 자식 컴포넌트에서는 전달받는 데이터가 어떤 부모 컴포넌트에서 전달되는지 확인이 안된다는 단점이 있다. 

 

 

Template refs

Vue 개발 시 특별한 경우가 아니면 HTML 객체에 바로 접근해서 코드를 구현해야할 일은 없다.

하지만 어쩔 수 없이 자바스크립트에서 HTML 객체에 바로 접근해야 한다면 HTML 태그에 id 대신 ref를 사용하면 된다. 

 

<input type="text" ref="title" />

this.$refs를 이용해서 ref 속성에 지정된 이름으로 HTML 객체에 접근이 가능해 진다. 

this.$refs.title.focus();

 

 

 

 

 

 

 

 

※ 출처 고승원Vue.js 프로젝트 투입 일주일 전, 비제이퍼블릭, p152-172.

320x100
반응형