Front-End프로그래밍

[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()를 다시 호출해야해

  1. ScheduleTypePopup → 저장 → 닫힘
  2. 닫힐 때 부모에 이벤트 전달 (emit("closePopup"))
  3. 부모에서 이 이벤트를 받음 → 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. 부모에서 ScheduleSettingPopupgetList() 호출

부모는 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 활용)

  • ScheduleSettingPopupemit("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 or setting) 기억 → 닫힐 때 분기해서 알림
  • ScheduleSettingPopup에서 열렸던 경우엔 getList() 자동 호출 가능

handleSchedTypePopup() 팝업을 닫을때 ScheduleSettingPopup() 팝업의 initSchedulePopup() 함수를 호출해야하는데….

즉, ScheduleTypePopup이 닫힐 때 ScheduleSettingPopupinitSchedulePopup()을 호출하고 싶은 거죠.
그럼 이벤트 → 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>

✅ 이렇게 하면 흐름이 딱 맞아요:

  1. ScheduleSettingPopup에서 ScheduleTypePopup 열기
  2. ScheduleTypePopup 닫힘 → 부모(Main.vue)에서 이벤트 수신
  3. 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) → 취소 닫힘 → 아무것도 안함

혹시 saveYnboolean 대신 "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() 쓸 수 있어요!”

error: Content is protected !!