[Vue3, TypeScript] 부모창 > 팝업 > 팝업 호출 및 저장 및 UI 컨트롤 방법 : defineExpose() 이용 필수!! 컴포넌트 내부의 특정 함수나 변수를 부모 컴포넌트에서 ref를 통해 접근할 수 있도록 노출하는 역할
ScheduleSettingPopup
에서 내부에서 ScheduleTypePopup
을 호출하고 싶다면, SettingPopup.vue
안에서 emit
을 올리고, 부모에서 받아서 handleTypePopup()
을 실행하는 구조가 가장 깔끔합니다.
SettingPopup.vue
<script setup lang="ts">
import { defineEmits } from "vue";
const emit = defineEmits(["openScheduleType"]);
const openTypePopup = () => {
emit("openScheduleType");
};
</script>
<template>
<button @click="openTypePopup">스케줄 타입 관리 열기</button>
</template>
부모창
<ScheduleSettingPopup ref="schedPopRef" @openScheduleType="handleSchedTypePopup" />
TypePopup.vue
const scheduleTypePopupRef = ref();
const handleTypePopup = () => {
typePopupRef.value.openPopup();
};
<TypePopup ref="scheduleTypePopupRef" />
ScheduleSettingPopup 닫을 때, cardUserListStore.getUserList()를 호출하고 싶어
ScheduleSettingPopup
은 부모 컴포넌트(올려주신 화면)에서 ref
로 열고 닫고 관리하고 있으니까,
닫힐 때 부모에게 알려주는 이벤트(emit
)를 만들고, 부모에서 그걸 받아서 cardUserListStore.getUserList()
를 호출하면 됩니다.
1. ScheduleSettingPopup.vue
에서 닫기 이벤트 emit
<script setup lang="ts">
import { defineEmits } from 'vue';
const emit = defineEmits(['closePopup']);
const closePopup = () => {
// 내부 닫기 로직 수행
emit('closePopup'); // 부모에 알림
};
</script>
<template>
<PopupContainer title="스케줄 설정"
:is-open="isOpen"
@close-popup="closePopup">
<!-- 내용 -->
</PopupContainer>
</template>
부모(지금 올려주신 화면)에서 받아서 처리
<!-- ** 스케줄 설정 팝업 (컴포넌트) -->
<ScheduleSettingPopup
ref="schedPopRef"
@closePopup="handleSchedPopupClose" />
.....
.ts파일
const handleSchedPopupClose = async () => {
await cardUserListStore.getUserList();
};
이렇게 하면 ScheduleSettingPopup
이 닫힐 때마다 자동으로cardUserListStore.getUserList()
가 호출되어 리스트가 새로고침됩니다.
부모창에서 이렇게 보내면 자식창에서 2개를 어떻게 받아?
defineEmits
는 컴포넌트마다 한 번만 호출해야 합니다.
여러 번 쓸 수 있는 게 아니고, 한 곳에서 모든 이벤트를 배열로 정의하면 됩니다.
잘못된 코드 (X)
const emit = defineEmits(["openScheduleType"]);
const emit2 = defineEmits(["closePopup"]);
이건 안 됩니다 ❌ (두 번 호출하면 타입도 꼬이고 런타임 경고 납니다).
올바른 코드 (O)
<script setup lang="ts">
const emit = defineEmits(["openScheduleType", "closePopup"]);
const openTypePopup = () => {
emit("openScheduleType");
};
const closePopup = () => {
emit("closePopup");
};
</script>
부모에서는 그대로 두면 됩니다
<ScheduleSettingPopup
ref="schedPopRef"
@openScheduleType="handleSchedTypePopup"
@closePopup="handleSchedPopupClose"
/>
요약
defineEmits
는 여러 번 쓰지 말고, 한 번에 이벤트 이름 배열로 정의하세요.- 자식에서
emit("openScheduleType")
,emit("closePopup")
각각 호출하면 부모에서 이벤트가 잘 받아집니다.
이제 마지막으로 하나 남았다. ScheduleTypePopup()에서 저장 후 창이 닫히면 ScheduleSettingPopup에서 getList()를 다시 호출해야해
- ScheduleTypePopup → 저장 → 닫힘
- 닫힐 때 부모에 이벤트 전달 (
emit("closePopup")
) - 부모에서 이 이벤트를 받음 →
ScheduleSettingPopup
안의getList()
호출
1. ScheduleTypePopup.vue
(자식)
<script setup lang="ts">
const emit = defineEmits(["closePopup"]);
const saveAndClose = async () => {
// 저장 로직 실행
await saveType();
// 닫기 알림 부모로 emit
emit("closePopup");
};
</script>
<template>
<PopupContainer title="스케줄 타입 관리"
:is-open="isOpen"
@close-popup="() => emit('closePopup')">
<button @click="saveAndClose">저장</button>
</PopupContainer>
</template>
2. 부모 (지금 올려주신 화면)
<ScheduleTypePopup
ref="scheduleTypePopupRef"
@closePopup="handleSchedTypePopupClose"
/>
3. 부모에서 ScheduleSettingPopup
의 getList()
호출
부모는 schedPopRef
를 이미 갖고 있으니까, 그걸로 getList()
실행해주면 됩니다.
const handleSchedTypePopupClose = async () => {
// ScheduleTypePopup 닫힐 때 ScheduleSettingPopup의 getList 실행
if (schedPopRef.value) {
await schedPopRef.value.getList();
}
};
👉 핵심은 ScheduleSettingPopup.vue
안에서 getList()
를 expose()
해주는 겁니다.
4. ScheduleSettingPopup.vue
<script setup lang="ts">
import { defineExpose } from "vue";
const getList = async () => {
// 여기서 실제 리스트 새로고침 로직
await fetchScheduleList();
};
// 부모에서 접근할 수 있도록 노출
defineExpose({
getList
});
</script>
ScheduleTypePopup 팝업을 ScheduleSettingPopup에서 호출했는지, 아니면 부모창에서 호출했는지에 따라 ScheduleTypePopup창을 닫을때 액션이 바뀌어야해
아하 ✅ 핵심은
- ScheduleTypePopup 닫힐 때
- 누가 열었는지(부모 vs ScheduleSettingPopup) 에 따라 다른 액션을 해야 한다는 거군요.
이럴 때는 보통 context
(호출자) 정보를 prop으로 내려주거나, emit payload로 넘겨서 구분합니다.
방법 1: 열 때 context 넘기기 (추천)
부모.vue
<!-- 부모에서 직접 열 때 -->
<ScheduleTypePopup
ref="scheduleTypePopupRef"
@closePopup="handleSchedTypePopupClose"
/>
const handleSchedTypePopup = () => {
scheduleTypePopupRef.value.openPopup("parent"); // 부모에서 열었다
};
const handleSchedTypePopupClose = (context: "parent" | "setting") => {
if (context === "setting") {
// ScheduleSettingPopup에서 호출한 경우 → ScheduleSettingPopup 갱신
schedPopRef.value.getList();
} else {
// 부모에서 직접 호출한 경우 → 부모 로직
cardUserListStore.getUserList();
}
};
ScheduleSettingPopup.vue
<ScheduleTypePopup
ref="scheduleTypePopupRef"
@closePopup="(ctx) => $emit('typePopupClosed', ctx)"
/>
const openTypePopup = () => {
scheduleTypePopupRef.value.openPopup("setting"); // ScheduleSettingPopup에서 열었다
};
ScheduleTypePopup.vue
<script setup lang="ts">
const emit = defineEmits<{
(e: "closePopup", context: "parent" | "setting"): void
}>();
const context = ref<"parent" | "setting">("parent");
const openPopup = (from: "parent" | "setting") => {
context.value = from;
isOpen.value = true;
};
const closePopup = () => {
isOpen.value = false;
emit("closePopup", context.value);
};
defineExpose({ openPopup });
</script>
방법 2: 부모에서만 구분 (emit 그대로, ref 활용)
ScheduleSettingPopup
→emit("openScheduleType")
로 부모 호출- 부모에서
handleSchedTypePopup("setting")
같은 식으로 구분 - 닫힐 때
context
를 부모가 기억하고 있다가, close 시점에 맞는 동작 실행
이 경우 부모에 context 상태값 저장:
const typePopupContext = ref<"parent" | "setting">("parent");
const handleSchedTypePopup = (from: "parent" | "setting" = "parent") => {
typePopupContext.value = from;
scheduleTypePopupRef.value.openPopup();
};
const handleSchedTypePopupClose = () => {
if (typePopupContext.value === "setting") {
schedPopRef.value.getList();
} else {
cardUserListStore.getUserList();
}
};
하나로 합쳐봐
const handleSchedTypePopup = () => { scheduleTypePopupRef.value.openPopup(“parent”); };
const handleSchedTypePopup = () => { scheduleTypePopupRef.value.openPopup(“settings”); };
// 하나로 합친 함수
const handleSchedTypePopup = (from: "parent" | "setting" = "parent") => {
scheduleTypePopupRef.value.openPopup(from);
};
- 부모에서 직접 호출할 때:
<button @click="handleSchedTypePopup('parent')">스케줄 타입관리</button>
ScheduleSettingPopup 안에서 호출할 때:
const openTypePopup = () => {
emit("openScheduleType"); // 부모에게 알려주고
// 부모에서 handleSchedTypePopup('setting') 실행
};
최종 코드
// 기본값은 "parent"
const handleSchedTypePopup = (from: "parent" | "setting" = "parent") => {
scheduleTypePopupRef.value.openPopup(from);
};
부모창에서 열 때 (기본값 “parent”)
<!-- 기본값 사용 -->
<button @click="handleSchedTypePopup()">스케줄 타입 관리</button>
ScheduleSettingPopup → 부모에 알릴 때
const openTypePopup = () => {
emit("openScheduleType", "setting"); // 부모에 "setting"으로 알림
};
부모에서 이벤트 받을 때
<ScheduleSettingPopup
@openScheduleType="handleSchedTypePopup"
/>
const handleSchedTypePopup = (from: "parent" | "setting" = "parent") => {
scheduleTypePopupRef.value.openPopup(from);
};
1. ScheduleTypePopup.vue
<script setup lang="ts">
import { ref } from "vue";
const emit = defineEmits(["closePopup"]);
const fromContext = ref<"parent" | "setting">("parent");
// 열 때 호출되는 메서드
const openPopup = (from: "parent" | "setting" = "parent") => {
fromContext.value = from;
// 실제 팝업 열리는 로직...
};
// 닫을 때 호출되는 메서드
const closePopup = () => {
if (fromContext.value === "parent") {
// 부모창에서 연 경우 → 부모에서만 처리
emit("closePopup", "parent");
} else if (fromContext.value === "setting") {
// ScheduleSettingPopup에서 연 경우 → 설정팝업에 처리 위임
emit("closePopup", "setting");
}
// 실제 팝업 닫는 로직...
};
// 외부에서 접근할 수 있게 메서드 노출
defineExpose({ openPopup, closePopup });
</script>
2. ScheduleSettingPopup.vue
<ScheduleTypePopup
ref="scheduleTypePopupRef"
@closePopup="handleSchedTypePopupClose"
/>
<script setup lang="ts">
const handleSchedTypePopupClose = (from: "parent" | "setting") => {
if (from === "setting") {
// 여기서 getList() 다시 호출!
getList();
}
};
</script>
3. 부모 컴포넌트
<ScheduleSettingPopup
@openScheduleType="handleSchedTypePopup"
/>
<ScheduleTypePopup
ref="scheduleTypePopupRef"
@closePopup="handleSchedTypePopupClose"
/>
<script setup lang="ts">
const handleSchedTypePopup = (from: "parent" | "setting" = "parent") => {
scheduleTypePopupRef.value.openPopup(from);
};
const handleSchedTypePopupClose = (from: "parent" | "setting") => {
if (from === "parent") {
// 부모에서 연 경우 닫힐 때 할 동작
console.log("부모에서 ScheduleTypePopup 닫힘");
} else if (from === "setting") {
// setting에서 닫힘은 ScheduleSettingPopup에서 이미 처리
console.log("setting에서 닫힘 → ScheduleSettingPopup에서 처리됨");
}
};
</script>
✅ 이렇게 하면
ScheduleTypePopup
이 어디서 열렸는지(parent
orsetting
) 기억 → 닫힐 때 분기해서 알림ScheduleSettingPopup
에서 열렸던 경우엔 getList() 자동 호출 가능
handleSchedTypePopup() 팝업을 닫을때 ScheduleSettingPopup() 팝업의 initSchedulePopup() 함수를 호출해야하는데….
즉, ScheduleTypePopup
이 닫힐 때 ScheduleSettingPopup
의 initSchedulePopup()
을 호출하고 싶은 거죠.
그럼 이벤트 → ref 접근 방식으로 처리하면 깔끔합니다.
1. ScheduleSettingPopup.vue
<script setup lang="ts">
import { ref } from "vue";
// 외부에서 호출 가능하게 init 함수 정의
const initSchedulePopup = () => {
console.log("📌 ScheduleSettingPopup initSchedulePopup 실행됨");
// 👉 여기서 schedTypeListStore.getList() + datePickerStore.setAttr() 등 호출
};
defineExpose({ initSchedulePopup });
</script>
2. ScheduleTypePopup.vue
<script setup lang="ts">
import { ref } from "vue";
const emit = defineEmits(["closePopup"]);
const fromContext = ref<"parent" | "setting">("parent");
const openPopup = (from: "parent" | "setting" = "parent") => {
fromContext.value = from;
// 실제 팝업 열기
};
const closePopup = () => {
emit("closePopup", fromContext.value);
// 실제 닫기 처리
};
defineExpose({ openPopup, closePopup });
</script>
3. 부모 컴포넌트 (예: Main.vue
)
<ScheduleSettingPopup
ref="scheduleSettingPopupRef"
@openScheduleType="(from) => handleSchedTypePopup(from)"
/>
<ScheduleTypePopup
ref="scheduleTypePopupRef"
@closePopup="handleSchedTypePopupClose"
/>
<script setup lang="ts">
import { ref } from "vue";
const scheduleSettingPopupRef = ref();
const scheduleTypePopupRef = ref();
const handleSchedTypePopup = (from: "parent" | "setting" = "parent") => {
scheduleTypePopupRef.value.openPopup(from);
};
const handleSchedTypePopupClose = (from: "parent" | "setting") => {
if (from === "setting") {
// 여기서 ScheduleSettingPopup의 initSchedulePopup 호출
scheduleSettingPopupRef.value?.initSchedulePopup();
}
};
</script>
✅ 이렇게 하면 흐름이 딱 맞아요:
ScheduleSettingPopup
에서ScheduleTypePopup
열기ScheduleTypePopup
닫힘 → 부모(Main.vue
)에서 이벤트 수신from === "setting"
일 때 →scheduleSettingPopupRef.value.initSchedulePopup()
실행
질의 :
const handleSchedTypePopupClose = (context: “parent” | “setting”) => {
if (context === “setting”) { // ScheduleSettingPopup에서 호출한 경우 → ScheduleSettingPopup 갱신 schedPopRef.value.initInfo(); } else { // 부모에서 직접 호출한 경우 } };
handleSchedTypePopupClose 에 saveYn 파라미터 하나 더 추가하고 싶어
답변:
수정된 코드
const handleSchedTypePopupClose = (
context: "parent" | "setting",
saveYn: boolean
) => {
if (context === "setting") {
// ScheduleSettingPopup에서 호출한 경우 → ScheduleSettingPopup 갱신
if (saveYn) {
schedPopRef.value.initInfo();
}
} else {
// 부모에서 직접 호출한 경우
if (saveYn) {
// 필요하다면 여기서도 추가 처리
}
}
};
ScheduleTypePopup 닫을 때 emit
const closePopup = (saveYn: boolean) => {
emit("closePopup", "setting", saveYn);
};
부모에서 받기
<ScheduleTypePopup
ref="scheduleTypePopupRef"
@closePopup="handleSchedTypePopupClose"
/>
✅ 이렇게 하면
handleSchedTypePopupClose("setting", true)
→ 저장 후 닫힘 →initInfo()
호출handleSchedTypePopupClose("setting", false)
→ 취소 닫힘 → 아무것도 안함
혹시 saveYn
을 boolean
대신 "Y" | "N"
문자열로 쓰고 싶으세요? (실제 API랑 맞추려면 보통 "Y"/"N"
을 많이 쓰더라구요)
defineExpose()
정의 및 사용예
defineExpose()
는 Vue 3 <script setup>
전용 기능인데,
컴포넌트 내부의 특정 함수나 변수를 부모 컴포넌트에서 ref를 통해 접근할 수 있도록 노출하는 역할을 합니다.
예시: ScheduleTypePopup.vue
<script setup lang="ts">
const openPopup = () => {
console.log("📌 팝업 열림");
};
// 부모에서 접근 가능하도록 노출
defineExpose({
openPopup,
});
</script>
부모에서 사용
<ScheduleTypePopup ref="schedTypePopupRef" />
<script setup lang="ts">
import { ref, onMounted } from "vue";
const schedTypePopupRef = ref();
onMounted(() => {
// 자식에서 defineExpose로 노출한 함수 접근 가능
schedTypePopupRef.value.openPopup();
});
</script>
✅ 정리하면:
- 원래 자식 컴포넌트의 내부 함수/변수는 부모가 직접 접근 불가
defineExpose({ ... })
를 쓰면 부모에서ref.value.xxx
로 접근 가능- 따라서
openPopup
,closePopup
,initSchedulePopup
같은 자식의 동작을 부모가 직접 호출해야 할 때 꼭 필요
👉 즉,defineExpose({ openPopup })
→ “이 컴포넌트 ref를 통해 openPopup()
쓸 수 있어요!”