안녕하심까!!!
어쩌다보니 이제야 쓰게되는 블로그 글 ㅎㅅaㅎ... (죄삼다! OTL)
개념을 정확하게 알고 정보를 전달하기보다는
제가 코드 짜면서 생각했던 것과 공부한 걸 팀원들에게 전달하는 게 목적이기 때문에 !!
말은 편하게 하겠습니닷 ㅎㅎ
개발 배경부터 설명드리자면~~
저는 바우처&리워드쪽 백엔드 코드를 어느정도 마무리하고 더 필요한 api를 뽑아내기 위해서 프론트 작업으로 넘어왔답니다
일단 데이터가 화면에 제대로 찍히는 지 부터 확인하기 위해서 바우처 리스트를 출력하는 화면부터 만들어보기로 했는데요
결과는 다음과 같았습니다!
와아아~~~ 개쩐다ㅋㅋ
그런데 순간 드는 생각...
잠깐... 이 테이블 형식을 저장해두고 다른 컴포넌트에서 갖다 쓸 순 없을까?!?
이게 무슨 말이냐 ?
객체지향 수업시간에 항상 나오는 붕어빵틀로 예시로 들자면
이 테이블 형식을 붕어빵 틀로 삼고
고객 리스트, 직원 리스트, 바우처 리스트 등등을 이 붕어빵 틀로 찍어내자는 겁니당
물론 테이블 컬럼명이랑 가져와야하는 데이터들이 다르니 그것들은 각 컴포넌트에 맞춰서 커스텀을 하는 거죵
붕어빵 소에 따라 팥 붕어빵, 슈크림 붕어빵, 피자 붕어빵이 되는 것 마냥 ㅋㅂㅋ (안 웃김.)
Vue 공부를 시작하셨다면 아시겠지만 !
- props를 통해 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 수 있고
- 한 컴포넌트(부모 컴포넌트)에서 컴포넌트(자식 컴포넌트)를 랜딩할 수는 있는데
지금 이 상황은 부모 컴포넌트에서 여러 개의 자식 컴포넌트를 바꿔 끼려는 게 아니라
여러 부모 컴포넌트에서 한 자식 컴포넌트를 가져와서 커스텀해서 쓰려는 상황.
.
.
.
.
.
해줘.
가 아니라 구글링을 해봤습니다.
https://labs.brandi.co.kr//2020/02/04/chunbs.html
여기에 설명이 너~~무 잘 돼있어서 꼭 먼저 읽어보시길 바라고 (브랜디의 천보성 팀장님. 개큰감사 드립니다.)
제가 앞으로 드리는 설명에서는 위 포스팅을 기반으로 이게 저희 코드에 어떻게 적용 되었는지 + vue3이상은 v-slot이라는 조금은 다른 문법을 사용해야하기 때문에 어떻게 변형되었는지를 위주로 봐주시면 될 것 같습니다
일단은 slot을 사용하지 않은 기존의 VoucherList 컴포넌트는 어떻게 작성되었을 지 먼저 볼까요?
아 참고로 프론트 개발 전에 의논했던 대로 views폴더 안에는 상단 메뉴바에서 메뉴 클릭시 이동되는 페이지들이 있고
components 에서는 페이지에 들어가는 구성 컴포넌트들이 있습니다
저희는 VoucherPage컴포넌트에서 VoucherList 컴포넌트를 띄우는 것!
👇🏻 VoucherList.vue
<template>
<div class="voucher-list-container">
<div class="voucher-list">
<h2>바우처 목록</h2>
<div v-if="vouchers.length > 0">
<div class="voucher-header">
<span class="voucher-column">회원 ID</span>
<span class="voucher-column">회원명</span>
<span class="voucher-column">발급 직원 ID</span>
<span class="voucher-column">발급 직원명</span>
<span class="voucher-column">금액</span>
<span class="voucher-column">바우처 코드</span>
</div>
<ul class="voucher-items">
<li
v-for="voucher in vouchers"
:key="voucher.id"
class="voucher-item"
>
<div class="voucher-info">
<span class="voucher-customerId">{{ voucher.customerId }}</span>
<span class="voucher-customerName">{{
voucher.customerName
}}</span>
<span class="voucher-employeeId">{{ voucher.employeeId }}</span>
<span class="voucher-employeeName">{{
voucher.employeeName
}}</span>
<span class="voucher-amount">{{ voucher.amount }}</span>
<span class="voucher-voucherCode">{{ voucher.voucherCode }}</span>
</div>
</li>
</ul>
</div>
<p v-else class="empty-message">바우처가 없습니다.</p>
<p v-if="errorMessage" class="error-message">{{ errorMessage }}</p>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
vouchers: [],
errorMessage: "",
};
},
created() {
this.fetchVouchers();
},
methods: {
async fetchVouchers() {
try {
const response = await axios.get(
"http://localhost:8080/api/v1/vouchers"
);
this.vouchers = response.data.data; // response.data.data로 접근
this.errorMessage = "";
} catch (error) {
console.error("바우처 목록을 불러오는 중 에러 발생:", error);
this.errorMessage = "바우처 목록을 불러오는 중 에러가 발생했습니다.";
}
},
},
};
</script>
<style scoped>
.voucher-list-container {
max-width: 800px;
margin: 0 auto;
}
.voucher-list {
background-color: #f5f5f5;
border-radius: 10px;
padding: 20px;
}
.voucher-header {
display: flex;
justify-content: space-between;
font-weight: bold;
margin-bottom: 10px;
}
.voucher-column {
flex: 1;
text-align: center;
}
.voucher-items {
list-style-type: none;
padding: 0;
}
.voucher-item {
background-color: #ffffff;
padding: 15px;
border-radius: 5px;
margin-bottom: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.voucher-info {
display: flex;
justify-content: space-between;
}
.voucher-info span {
flex: 1;
text-align: center;
}
.empty-message,
.error-message {
margin-top: 20px;
text-align: center;
color: #555555;
}
.error-message {
color: red;
}
</style>
VoucherPage에서 이런 VoucherList를 띄우는 것 처럼
CustomerPage에선 이런 CustomerList를, EmployeePage에선 이런 EmployeeList를 불러와야하자나요?
이런 데이터 테이블의 형식과 CSS를 그대로 적용하게 하는, 붕어빵'틀'이 되는 컴포넌트를 "DataTable"이라는 이름으로 만들어봅시다!
👇🏻 DataTable.vue ( template / script / css 나눠서 )
<template>
<div class="list-container">
<div class="list">
<slot name="list-name"></slot>
<div class="header">
<slot name="header"></slot>
</div>
<ul class="items">
<slot name="items" :data="data"></slot>
</ul>
<p v-if="!hasData" class="empty-message">데이터가 없습니다.</p>
<p v-if="errorMessage" class="error-message">{{ errorMessage }}</p>
</div>
</div>
</template>
부모 컴포넌트에서 커스텀 될 부분을 slot 태그로 비워두고 name 속성으로 커스텀되는 부분을 구분합니다.
<slot name="list-name"> 은 리스트명을,
<slot name="header"> 은 리스트의 컬럼명들을,
<slot name="items"> 은 리스트의 데이터들을 담는 부분입니다.
<script>
import axios from "axios";
export default {
props: {
apiUrl: {
type: String,
required: true,
},
},
data() {
return {
data: [],
errorMessage: "",
};
},
computed: {
hasData() {
return this.data && this.data.length > 0;
},
},
created() {
this.fetchData();
},
methods: {
async fetchData() {
try {
const response = await axios.get(this.apiUrl);
this.data = response.data.data;
this.errorMessage = "";
} catch (error) {
console.error("데이터를 불러오는 중 에러 발생:", error);
this.errorMessage = "데이터를 불러오는 중 에러가 발생했습니다.";
}
},
},
};
</script>
DataTable을 갖다 쓰는 부모 컴포넌트는 어차피 목록을 출력하는 컴포넌트일테니
목록을 불러오는 메서드도 DataTable에서 제너럴하게 정의하고 재사용성을 늘리는 게 좋겠죠?
부모 컴포넌트에서 apiUrl을 전달하면 DataTable에서 정의한 fetchData 메서드를 해당 apiUrl에 맞춰 실행합니다.
부모 컴포넌트는 리스트 컴포넌트일테니 목록을 불러오는 api(ex."http://localhost:8080/api/v1/vouchers")를 전달하면 우리가 지정한response가 response.data로 들어오는데
저희가 response값을 code, message, data로 나눈 result response로 감싸놔서 목록을 불러오려면 response.data.data로 불러와야합니다!
<style scoped>
.list-container {
max-width: 800px;
margin: 0 auto;
}
.list {
background-color: #f5f5f5;
border-radius: 10px;
padding: 20px;
}
.header {
display: flex;
justify-content: space-between;
font-weight: bold;
margin-bottom: 10px;
}
.items {
list-style-type: none;
padding: 0;
}
:deep(.item) {
background-color: #ffffff;
padding: 15px;
border-radius: 5px;
margin-bottom: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
:deep(.item-info) {
display: flex;
justify-content: space-between;
}
:deep(.item-info span) {
flex: 1;
text-align: center;
}
.empty-message,
.error-message {
margin-top: 20px;
text-align: center;
color: #555555;
}
.error-message {
color: red;
}
</style>
CSS 부분에서 봐야할 것은 :deep 이라는 셀렉터인데요
DataTable 템플릿에서 설정한 css 클래스의 style은 당연히 여기서 작성이 가능하지만
이 컴포넌트를 갖다 쓰는 부모 컴포넌트의 스타일도 여기서 지정해주고 싶을 때 이걸 쓰면 된답니당
(물론 부모 컴포넌트에서도 DataTable 내의 부분들을 말하는 것! 마지막에 나오는 VoucherList 코드를 참고해주세요)
VouhcerList 템플릿의 item 클래스에 CSS를 적용시키고싶다면
:deep(.item-info) {
display: flex;
justify-content: space-between;
}
이렇게 써주면 된다는 겁니당
그래서.. VoucherList 뿐만 아니라 DataTable을 갖다 쓰는 List 관련 컴포넌트들의 css class 명들을 규칙에 맞게 통일 시켜준다면
각 컴포넌트에 매번 길게 CSS를 설정해주지 않아도 되겠다는 것. 쯤은 다들 예상하셨겠쬬?! 😎
마지막으로 DataTable 컴포넌트를 갖다 쓰도록 수정한 VoucherList 컴포넌트를 한 번 보실까요?
👇🏻 VoucherList.vue
<template>
<DataTable apiUrl="http://localhost:8080/api/v1/vouchers">
<template v-slot:list-name>
<h2>바우처 목록</h2>
</template>
<template v-slot:header>
<span>회원 ID</span>
<span>회원명</span>
<span>발급 직원 ID</span>
<span>발급 직원명</span>
<span>금액</span>
<span>바우처 코드</span>
</template>
<template v-slot:items="{ data }">
<li v-for="voucher in data" :key="voucher.id" class="item">
<div class="item-info">
<span>{{ voucher.customerId }}</span>
<span>{{ voucher.customerName }}</span>
<span>{{ voucher.employeeId }}</span>
<span>{{ voucher.employeeName }}</span>
<span>{{ voucher.amount }}</span>
<span>{{ voucher.voucherCode }}</span>
</div>
</li>
</template>
</DataTable>
</template>
<script>
import DataTable from "./DataTable.vue";
export default {
components: { DataTable },
};
</script>
<style scoped></style>
DataTable 태그로 DataTable 컴포넌트를 불러오고, apiUrl도 props로 전달합니다.
<template v-slot:list-name>, <template v-slot:header>, <template v-slot:items>이 각각
DataTable에서 <slot name="list-name">, <slot name="header">, <slot name="items"> 로 비워둔 부분을
해당 컴포넌트에 맞게 커스텀하는 부분입니다.
DataTable은 전달받은 apiUrl을 통해 데이터를 불러오고 data 속성에 저장한 뒤 data를 items 슬롯에 전달합니다. (DataTable 코드 참고)
VoucherList는 v-slot:items="{ data }" 를 통해 data를 받아서, data에 있는 항목들을 v-for을 사용해 반복하여 리스트를 출력합니다.
처음에 작성한 VoucherList 코드보다 훨씬 간결해졌쬬?
이렇게 slot을 활용하면 DataTable 하나만 구현해 두면 List 형식의 컴포넌트에서 테이블을 도장 찍듯이 찍어낼 수 있답니다
코드가 간결해질 뿐만 아니라 템플릿 형식과 CSS도 통일시켜줄 수 있다니!! 완전 럭키비키잖아~?🤭🍀
일단은 빠른 실습을 위해 간결하게 slot 코드를 작성해보았는데요
이제 잘 다듬어서 CustomerList, EmployeeList 등 모든 리스트 컴포넌트에 예쁘게 적용될 수 있도록 노력해봐야겠습니당
아자아자 !!! ٩(*•̀ᴗ•́*)و
ps. 개열시미 썼으니까 댓글 써주세요 안 써주면 이게 마지막 포스팅이 될 것임...
'Vue' 카테고리의 다른 글
[vue3 Quasar with pinia] 로그인 및 인증(2) (0) | 2024.06.13 |
---|---|
[vue3 with pinia] 로그인 및 인증(1) (0) | 2024.06.13 |