Search

공통 모달과 데이터 전송 - Emit 과 Props

태그

공통 모달 생성하기

부트스트랩을 사용하고 싶었으나, 왠지모르게 자꾸 실패해서 모달을 만들었습니다. (배보다 배꼽이 더 커져버림) 먼저 공통 모달을 어떻게 생성 했는지 부터 확인 해볼까요?
설명은 주석으로 달겠습니다.
<script setup lang="ts"> import { library } from '@fortawesome/fontawesome-svg-core'; import { faCircleXmark } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; library.add(faCircleXmark); // 부모 컴포넌트로부터 받을 데이터 입니다. const props = defineProps({ isOpen: Boolean }); // 자식 컴포넌트로부터 보낼 데이터 입니다. const emits = defineEmits(['close']); // 닫기 버튼을 누르면 위의 데이터를 보냅니다. const handleClose = () => { emits('close'); }; </script> <template> <div v-if="props.isOpen"> <!-- 부모 컴포넌트로부터 받을 데이터 --> <div class="modal-overlay"> <!-- 모달 뒤에 있을 배경 --> <div class="modal-content"> <!-- 모달 --> <div class="modal-button"> <button @click="handleClose"> <!-- 모달 닫기 버튼 --> <font-awesome-icon icon="fa-solid fa-circle-xmark" class="close-icon"/> </button> </div> <slot></slot> <!-- 컴포넌트가 들어갈 자리 --> </div> </div> </div> </template> <style lang="scss"> @import "../../styles/common/container.scss"; @import "../../styles/common/button.scss"; @import "../../styles/colors/_color.scss"; .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: flex-start; justify-content: center; } .modal-content { margin-top: 60px; background: white; padding: 35px 40px 60px 40px; border-radius: 12px; min-width: 350px; width: auto; max-height: 670px; overflow-y: scroll; } .modal-button { @include container(row, flex-end, center, 100%, 100%) } .modal-button button { border: none; background-color: transparent; padding: 0; margin: 0; } .close-icon{ font-size: 23px; color: $gray600; } </style>
HTML
복사

공통모달 사용하기

그럼 이제 이 모달을 어떻게 사용하는지 알아보겠습니다. 그전에 먼저 컴포넌트의 구조부터 살펴볼까요?

CeateBoard.vue

자식 컴포넌트는 부모 컴포넌트(BoardList.vue) 로 emit 을 사용하여 데이터를 전송합니다.
 CreateBoard.vue 는 TheModal.vue 의 자식 컴포넌트가 아닙니다. 삽입되었다고 보면 됩니다.
설명은 주석으로 달겠습니다.
<script setup lang="ts"> import { ref } from "vue"; import {createBoard} from "../../../api/projcet/BoardApi"; const title = ref(''); const emits = defineEmits(['board-created']); const handleProject = async () => { try { const response = await createBoard({ title: title.value }); if (response.status === 200) { // console.log('Board created successfully'); // 만약 200 상태를 받으면, 위에서 지정한 데이터가 부모 컴포넌트로 전송될거에요. emits('board-created'); } } catch (error) { console.error(error); } }; </script> <template> <!-- 모달의 slot 부분에 들어갈 부분입니다. --> <div class="board-modal-content"> <div class="board-modal-title"> 프로젝트 생성 </div> <form> <div> <div style="margin-top: 10px"> <div class="board-modal-label">프로젝트 이름을 입력해주세요.</div> <input v-model="title" type="text"> </div> <!-- 버튼을 누르게 되면 handleProject 함수가 실행 되겠군요! --> <button @click.prevent="handleProject" class="board-modal-button" type="submit">생성 하기</button> </div> </form> </div> </template>
HTML
복사

BoardList.vue

설명은 주석으로 달겠습니다.
<script setup lang="ts"> import {onMounted, ref} from "vue"; import {deleteBoard, displayBoards} from "../../../api/projcet/BoardApi.ts"; import TheModal from "../../common/TheModal.vue"; import CreateBoard from "./CreateBoard.vue"; import { library } from '@fortawesome/fontawesome-svg-core'; import { faPenToSquare, faSquarePlus } from '@fortawesome/free-regular-svg-icons'; import { faTrashCan } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; import UpdateBoard from "./UpdateBoard.vue"; import {useProjectStore} from "../../../store/ProjectStore.ts"; library.add(faPenToSquare, faTrashCan, faSquarePlus); const projectStore = useProjectStore(); const selectProject = (boardId, boardTitle) => { projectStore.setCurrentBoard(boardId, boardTitle); }; const boardsData = ref([]); // 여러 모달이 존재하는 경우, 상태를 분리해주세요. const isCreateModalOpen = ref(false); const isUpdateModalOpen = ref(false); // 생성 모달이 오픈되었을때 값을 변경해주어야 합니다. const openCreateModal = () => { isCreateModalOpen.value = true; }; const selectedBoardId = ref(''); const selectedBoardTitle = ref(''); const openUpdateModal = (boardId, boardTitle) => { selectedBoardId.value = boardId; selectedBoardTitle.value = boardTitle; isUpdateModalOpen.value = true; }; // 모달이 닫힐때 또한 값을 변경해주어야 겠죠. const closeModal = () => { isCreateModalOpen.value = false; isUpdateModalOpen.value = false; }; /** 이는 추가 구현사항입니다. 저의 경우 특정 요청에서 200 응답을 받았을 때 모달 창이 닫히게 구현하고 싶어서 아래와 같이 함수를 작성해주었습니다. **/ const onBoardCreated = () => { closeModal(); setTimeout(fetchBoards, 300); }; const handleDeleteBoard = async (boardId: string) => { try { await deleteBoard(boardId); await fetchBoards(); } catch (error) { console.error(error); } }; const fetchBoards = async () => { // console.log('Fetching boards...'); try { const response = await displayBoards(); boardsData.value = response.data; } catch (error) { console.error(error); } }; onMounted(fetchBoards); </script> <template> <div class="project-container"> <div class="title"> 프로젝트 </div> <!-- 모달창을 여는 데 사용할 버튼이나 요소를 만들어주세요 --> <div class="button-container" @click="openCreateModal"> <font-awesome-icon icon="fa-regular fa-square-plus" style="margin-right: 8px"/> <span>프로젝트 생성</span> </div> <div class="projects-list"> <router-link :to="`/project/${board.boardId}`" class="project" v-for="board in boardsData" :key="board.boardId" @click="selectProject(board.boardId, board.boardTitle)"> <div class="project-content"> <div class="project-title">{{ board.boardTitle }}</div> <div class="project-member-container"> <div v-for="member in board.members" :key="member.memberId" class="badge-created"> {{ member.username }} </div> </div> </div> <div class="board-button-container"> <font-awesome-icon icon="fa-regular fa-pen-to-square" @click="openUpdateModal(board.boardId, board.boardTitle)" style="margin-right: 15px;" class="board-icon"/> <font-awesome-icon icon="fa-solid fa-trash-can" @click="handleDeleteBoard(board.boardId)" class="board-icon"/> </div> </router-link> </div> </div> <!-- 아래에 모달 관련 요소들만 작성하면 한결 깔끔 하답니다. --> <TheModal :is-open="isCreateModalOpen" @close="closeModal"> <CreateBoard @board-created="onBoardCreated"/> </TheModal> <TheModal :is-open="isUpdateModalOpen" @close="closeModal"> <UpdateBoard :boardId="selectedBoardId" :boardTitle="selectedBoardTitle" @board-created="onBoardCreated"/> </TheModal> </template>
HTML
복사
:is-open 는 부모 컴포넌트의 데이터인 isCreatedModelOpen 과 연결하기 위해 사용합니다.
: 기호는 Vue.js 템플릿 문법에서 동적 속성(또는 props)을 나타냅니다. 이 기호는 v-bind: 의 축약형이며, 부모 컴포넌트의 데이터를 자식 컴포넌트의 속성에 바인딩 하기 위해 사용됩니다.
@board-created=”onBoardCreated” 가 의미하는 바는 다음과 같습니다. “자식 컴포넌트에서 board-created 라는 데이터를 보내면 onBoardCreated 함수를 호출해라” emit 으로 보낸 데이터와 같아야 하므로, 이점 꼭 유의해주세요.