일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 안드로이드스튜디오
- BAEKJOON
- issue
- DART
- Vue
- 코테
- cos pro
- 분할정복
- AndroidStudio
- DFS와BFS
- vuejs
- 개발
- Algorithm
- cos
- DFS
- 알고리즘
- cos pro 1급
- 백준
- codingtest
- 코딩테스트
- Flutter
- 안드로이드
- Python
- 파이썬
- 동적계획법과최단거리역추적
- 코드품앗이
- 동적계획법
- C++
- android
- django
- Today
- Total
Development Artist
[웹 서비스 A-Z][Vuejs] #13 Props, Emit 본문
지난 시간에는 Vuejs의 라이프 사이클과 훅들에 대해 간단히 테스트를 해보았는데요,
오늘은 부모 자식 컴포넌트 간 데이터 전달에 대해 알아보도록 하겠습니다.
데이터 전달을 위해서 props와 emit을 사용을 할텐데요, 간단한 예제 코드를 통해서 어떤식으로 사용을 하는지 확인해보도록 하겠습니다. 또한, modal 이라는 개념도 함께 설명을 하도록 하겠습니다.
우선 오늘 구현해볼 코드의 결과물 입니다.
지난 내용을 복습해 보자면, SFC는 Single-File Component로 하나의 파일이 하나의 컴포넌트로 간주한다고 하였습니다. 그렇다면 오늘 구현해볼 페이지는 몇 개의 컴포넌트로 구성하면 될까요?
하기 나름입니다.
하나의 파일로도 구성할 수 있겠지만, 다음의 파일들로 구성을 해보겠습니다.
- views/PropsEmitPage.vue
- components/PropsEmitMenu.vue
- components/PropsEmitProfile.vue
- components/PropsEmitModal.vue
각각의 파일이 어떤 컴포넌트일지 예상이 되시나요? 맞습니다.
PropsEmitPage를 부모 컴포넌트, PropsEmitMenu, PropsEmitProfile, PropsEmitModal을 자식 컴포넌트로 설정합니다. PropsEmitModal은 다른 자식 컴포넌트와는 조금 다른 것 같은데요, 밑에서 Modal이라는 개념과 함께 설명하도록 하겠습니다.
일단 PropsEmitPage를 라우팅 해주겠습니다. /propsemit 의 경로로 라우팅을 해주고, Homepage.vue에서 넘어갈 수 있게끔 router-link를 넣어줍니다.
``` 중략 ```
import PropsEmit from "./views/PropsEmitPage";
``` 중략 ```
const router = new VueRouter({
mode: "history",
routes: [
``` 중략 ```
{
path: "/propsemit",
component: PropsEmit,
},
],
});
export default router;
<template>
<div>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/propsemit">PropsEmit</router-link>
``` 중략 ```
</div>
</template>
``` 중략 ```
그러면 다음과 같이 PropsEmit 링크가 생기는 것을 확인할 수 있습니다.
자, 이제 PropsEmitPage를 만들어 봅시다. PropsEmitPage.vue를 아래와 같이 합니다.
여기서 profile의 이미지로 사용할 리소스를 assets/profile에 넣어두고 불러올 예정입니다. 따라서, jpg 형식의 아무 사진 4개를 ‘person_001’, ‘person_002’, ‘person_003’, ‘person_004’로 하여, assets/profile/ 아래에 넣어주세요. profile이란 폴더는 없을 겁니다. 따로 만들어 줍니다.
- PropsEmitPage.vue
<!-- views/PropsEmitPage.vue -->
<template>
<div>
<PropsEmitModal :profile="profiles[stateId]" :isAct="isModalAct" @closeModal="closeModalinPage" />
<PropsEmitMenu :menu="parentMenu" />
<nav>
<ul>
<PropsEmitProfile v-for="profile, i in profiles" :key="i" v-bind:profile="profile"
@openModal="openModalinPage($event)" />
</ul>
</nav>
</div>
</template>
<script>
import PropsEmitMenu from '../components/PropsEmitMenu.vue';
import PropsEmitProfile from '../components/PropsEmitProfile.vue';
import PropsEmitModal from '../components/PropsEmitModal.vue';
export default {
name: 'PropsEmitTest',
data() {
return {
stateId: 0,
isModalAct: false,
parentMenu: [['Home', '/'], ['About', '/about'], ['Profile', '/propsemit']],
profiles: [
{ id: '0', name: 'Cristina', age: 16, job: '농부' ,img: 'person_001' },
{ id: '1', name: 'David', age: 19, job: '마피아' ,img: 'person_002' },
{ id: '2', name: 'Matilda', age: 23, job: '역무원' ,img: 'person_003' },
{ id: '3', name: 'Sophia', age: 17, job: '국회의원' ,img: 'person_004' },
]
}
},
methods: {
openModalinPage(id) {
console.log('Page Open');
this.stateId = id;
this.isModalAct = true;
},
closeModalinPage() {
console.log('Page Close');
this.stateId = 0;
this.isModalAct = false;
}
},
components: {
PropsEmitMenu, PropsEmitProfile, PropsEmitModal
}
}
</script>
<style>
</style>
그리고 각각 자식 컴포넌트들의 코드도 넣어줍니다.
- PropsEmitMenu.vue
<!-- components/PropsEmitMenu.vue -->
<template>
<nav>
<ul class="menu">
<li class="menu-item" v-for="item, i in menu" :key="i">
<a :href="item[1]">{{ item[0] }}</a>
</li>
</ul>
</nav>
</template>
<script>
export default {
name: 'PropsEmitMenu',
props: {
menu: Array
}
}
</script>
<style>
.menu {
display: flex;
justify-content: center;
padding: 10px;
border-radius: 5px;
background: steelblue;
list-style: none;
}
.menu-item {
padding: 10px;
}
.menu-item a {
color: white;
text-decoration: none;
}
</style>
- PropsEmitProfile.vue
<!-- components/PropsEmitProfile.vue -->
<template>
<li class="profile">
<img class="profile-img" :src="getImgUrl(profile.img)"/>
<div class="profile-content">
<h3 class="content-name">{{ profile.name }}</h3>
<button class="content-btn" @click="openModalinHere(profile.id)">상세보기</button>
</div>
</li>
</template>
<script>
export default {
name: 'PropsEmitProfile',
props: {
profile: Object
},
methods: {
getImgUrl(pet) {
var images = require.context('../assets/profile/', false, /\\.jpg$/)
return images('./' + pet + ".jpg")
},
openModalinHere(id) {
console.log('Profile Open');
this.$emit('openModal', id);
}
}
}
</script>
<style>
.profile {
display: flex;
flex-direction: row;
justify-content: center;
flex-wrap: wrap;
align-items: center;
list-style: none;
margin: 10px;
}
.profile-img {
width: 30%;
height: 30%;
}
.profile-content {
margin-left: 10px;
}
.content-btn {
margin: 10px 0 10px;
width: 100%;
height: 30px;
background: rgb(39, 25, 25);
color: rgb(255, 255, 255);
/* font: var(--text_semi_bold1); */
font-size: 14px;
font-weight: 500;
border-radius: 20px;
transition: background .5s;
cursor: pointer;
}
</style>
- PropsEmitModal.vue
<!-- components/PropsEmitModal.vue -->
<template>
<div class="modal-bg" v-if="isAct">
<div class="modal-fg">
<h2>프로필 상세 정보</h2>
<img class="profile-modal-img" :src="getImgUrl(profile.img)" />
<h3>{{ profile.name }}</h3>
<div class="content-job">직업 : {{ profile.job }}</div>
<div>나이 : {{ profile.age }} 살</div>
<button class="modal-fg-btn" @click="closeModalinHere">닫기</button>
</div>
</div>
</template>
<script>
export default {
name: 'PropsEmitModal',
methods: {
getImgUrl(pet) {
var images = require.context('../assets/profile/', false, /\\.jpg$/)
return images('./' + pet + ".jpg")
},
closeModalinHere() {
console.log('Modal Close');
this.$emit('closeModal');
}
},
props: {
profile: Object,
isAct: Boolean
}
}
</script>
<style>
.modal-bg {
display: flex;
position: fixed;
flex-direction: column;
align-items: center;
background-color: rgba(0, 0, 0, 0.6);
width: 100vw;
height: 100vh;
}
.modal-fg {
display: flex;
position: absolute;
border-radius: 10px;
padding: 10px;
top: 10%;
flex-direction: column;
align-items: center;
background-color: rgb(180, 226, 174);
width: 80%;
max-width: 450px;
user-select: none;
}
.modal-fg-btn {
margin-top: 10px;
padding: 6px 12px;
font-size: 19px;
}
.profile-modal-img {
width: 42%;
height: 42%;
}
</style>
자, 이제 props와 emit에 대해 알아봅시다.
props는 부모→자식, emit은 자식→부모로 데이터를 전달합니다.
먼저 props 입니다.
PropsEmitPage.vue를 보시면, <script> 태그 내 data에 parentMenu와 profiles 배열이 하나씩 있습니다. menu는 각 인덱스마다 메뉴이름과 라우팅경로를 갖고 있고, profiles의 각 인덱스 별 객체는 id, name, age, job, img 를 가지고 있습니다. 이 데이터들을 props라는 것을 사용하여 자식 컴포넌트에 전달하겠습니다.
<template> 태그에 보시면 자식 컴포넌트들을 써주고 있습니다.
자식 컴포넌트 태그 안에 :[자식에서 사용할 값]=”[부모가 던질 값]”으로 속성을 주면 됩니다.
- <PropsEmitMenu> 태그: :menu=”parentMenu”
- <PropsEmitProfile> 태그: *v-for*="profile, i in profiles" :key="i" v-bind:profile="profile"
- <PropsEmitModal> 태그: :profile="profiles[stateId]" :isAct="isModalAct"
이렇게 부모 컴포넌트에서 던져주면 자식은 어떻게 사용을 할까요?
자식 컴포넌트에서는 props라는 옵션을 써줍니다. 부모에서 정의한 [자식에서 사용할 값]과 동일하게 props 안에 [자식에서 사용할 값]: 타입 으로 적어줍니다.
- PropsEmitMenu.vue
props: {
menu: Array
},
- PropsEmitProfile.vue
props: {
profile: Object
},
- PropsEmitModal.vue
props: {
profile: Object,
isAct: Boolean
}
이렇게 되면 component가 create 될 때, 부모→자식으로 데이터 전달이 이뤄집니다.
다음은 emit입니다.
자식에서 부모로 데이터를 전달할 경우도 생깁니다.
PropsEmitProfile 자식 컴포넌트에서 프로필을 상세보기를 클릭 했을 때 PropsEmitPage 부모 컴포넌트로 ‘프로필 상세 보기 페이지를 열어줘!’ 라고 말을 해야하거나, PropsEmitModal 자식 컴포넌트가 PropsEmitPage 부모 컴포넌트로 ‘상세 보기 페이지를 닫아줘!’ 라고 하는 등의 요청을 수행하는 경우입니다.
- PropsEmitProfile.vue
<button class="content-btn" @click="openModalinHere(profile.id)">상세보기</button>
상세보기를 클릭하면, openModalinHere 함수가 동작합니다.
openModalinHere(id) {
this.$emit('openModal', id);
}
openModalinHere 함수는 profile.id를 부모 컴포넌트의 openModal 이벤트로 던집니다.
PropsEmitPage 부모 컴포넌트에서는 openModel 이벤트가 동작을 하는데, openModalinPage를 수행하는 것입니다.
openModalinPage는 stateId 를 받은 profile.id 값으로 넣어주고, isModalAct를 true로 바꿔줍니다.
여기서 Modal 개념을 설명하면 좋을 것 같은데요, Modal은 다음과 같이 정의 할 수 있습니다.
다른 모든 페이지 콘텐츠 앞에 표시되고 비활성화되는 웹 페이지 요소입니다. 기본 콘텐츠로 돌아가려면 사용자는 작업을 완료하거나 닫아 모달에 참여해야 합니다. 모달은 종종 사용자의 주의를 웹 사이트 또는 애플리케이션의 중요한 작업이나 정보로 안내하는 데 사용됩니다.
따라서, isModalAct 값에 따라 PropsEmitModal 의 화면이 나타나고 사라지고 할 수 있습니다.
- PropsEmitModal.vue
<div class="modal-bg" v-if="isAct">
``` 보여질 부분 ```
</div>
v-if 속성을 이용하여 isAct가 true이면 보여질 부분을 노출시키게 됩니다.
Modal 창을 닫는 것도 마찬가지로 emit를 통해 isAct 값을 false로 바꾸는 것이겠죠?
한번 추적해보시면 좋을 것 같습니다.
다음 시간에는 API 를 통해 서버에 데이터를 요청하고 받아서 처리하는 시간을 가져보도록 하겠습니다.
감사합니다.
Addtional
props에 대해서 조금 더 설명을 하고자 합니다.
타입별로 부모 컴포넌트에서 써줄 수 있는 방식이 다양한데요, 참고하면 좋을 것 같습니다.
- Number
<PropsEmitProfile :age="42" />
<PropsEmitProfile :age="profile.age" />
- Boolean
<PropsEmitProfile is-male />
<PropsEmitProfile :is-male="false" />
<PropsEmitProfile :is-male="profile.isMale" />
- Array
<PropsEmitProfile :comment-ids="[234, 266, 273]" />
<PropsEmitProfile :comment-ids="profile.commentIds" />
- Object
<PropsEmitProfile
:dad="{
name: 'Veronica',
age: 43
}"
/>
<PropsEmitProfile :dad="profile.dad" />
또한, props로 데이터를 받을 때 옵션을 통해 커스텀할 수 있습니다. 부모로 부터 해당 데이터를 못 받는 경우 default 옵션을 통해 기본 값을 설정해줄 수 있습니다.
props: {
// Basic type check
// (`null` and `undefined` values will allow any type)
propA: Number,
// Multiple possible types
propB: [String, Number],
// Required string
propC: {
type: String,
required: true
},
// Number with a default value
propD: {
type: Number,
default: 100
},
// Object with a default value
propE: {
type: Object,
// Object or array defaults must be returned from
// a factory function. The function receives the raw
// props received by the component as the argument.
default(rawProps) {
return { message: 'hello' }
}
},
// Custom validator function
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// Function with a default value
propG: {
type: Function,
// Unlike object or array default, this is not a factory function - this is a function to serve as a default value
default() {
return 'Default function'
}
}
}
그리고 props는 readonly이기 때문에, 자식 컴포넌트에서 값을 변경하면 안됩니다. 따라서, 자식 컴포넌트의 computed() 수행 시 값을 변경하거나 함수 동작시 값을 변경하는 등은 할 수 없습니다.
'Project_Personal' 카테고리의 다른 글
[웹 서비스 A-Z][Deployment] #15 Architecture (0) | 2023.03.22 |
---|---|
[웹 서비스 A-Z][Vuejs] #14 API (0) | 2023.03.21 |
[웹 서비스 A-Z][Django] #12 ORM (0) | 2023.03.17 |
[웹 서비스 A-Z][Django] #11 Database (0) | 2023.03.15 |
[웹 서비스 A-Z][Django] #10 CORS (0) | 2023.03.13 |