Front-End프로그래밍

[Vue3, TypeScript] Vue props 전달 규칙 개념 정리 (kebab-case ↔ camelCase)

ChatGpt에게 질문을 던졌다. 질문 내용은 다음과 같다.

부모창에서 팝업 띄울때 props 파라미터로 user-no , isValid넘기는데, is-valid 로 안해도 잘 되는건가? Vue에서는 props 전달 규칙 때문에 혼동이 생길 수 있.

<CheckPopup v-if="selectedData" v-model:is-open="isAdminPopup"  
:phoneNm="seqMobile"
:user-no="selectedData.userNo" 
:seq="Number(seq)" 
:isValid="false" 
@after-confirm=" async () => { 
   resetPwClientInfoManager(seq); 
   alertMessage = messages.alert.initPasswd; 
   isAlertPopup = true; return; 
}"/> 

1. 부모 → 자식으로 props 전달할 때

부모 템플릿에서 kebab-case로 작성해야 합니다.
즉, 아래처럼 쓰는 게 올바른 방식이에요:

<CheckPopup
  :user-no="selectedData.userNo"
  :is-valid="false"
/>

2. 자식 컴포넌트에서 props 정의할 때

자식에서는 camelCase로 정의해야 합니다:

defineProps<{
  suerNo: string
  isValid: boolean
}>()

3. 왜 is-valid 안 써도 되는 경우가 있나?

  • Vue는 자동으로 kebab-casecamelCase 매핑을 해줍니다.
  • 즉, 부모에서 :isValid="false"라고 써도 내부적으로 is-valid로 변환돼서 props 매칭이 됩니다.

하지만 HTML attribute는 대소문자를 구분하지 않기 때문에
DOM에 그대로 렌더링되는 속성에서는 isValid 대신 is-valid로 처리됩니다.


정리

  • 공식 권장: 부모에서 kebab-case(is-valid) 로 쓰기
  • 자식에서 camelCase(isValid) 로 정의하기
  • 지금처럼 :isValid="false"라고 해도 동작은 하지만, 팀 코드 컨벤션이나 가독성 때문에 is-valid 쓰는 게 안전합니다.

👉 혹시 이 프로젝트는 Vue 3 + TypeScript 기반이죠? (Pinia도 쓰고 계신 걸로 알아요)
그러면 타입 추론 잘 되게 하려면 부모 쪽에서 is-valid로 맞추는 걸 추천드려요.


Vue3 + TypeScript + Pinia 기준으로 props 정의와 부모 호출 예시



자식 컴포넌트 (CheckPopup.vue)

<script setup lang="ts">
interface Props {
  phoneNm: string
  userNo: string
  seq: number
  isValid: boolean
  isOpen: boolean
}

const props = defineProps<Props>()

// 부모에서 v-model:is-open 으로 넘겨주니까 emit 도 선언
const emit = defineEmits<{
  (e: 'update:isOpen', value: boolean): void
  (e: 'after-confirm'): void
}>()
</script>

부모 컴포넌트 (예: AdminPage.vue)

<CheckPopup
  v-if="selectedData"
  :phone-nm="seqMobile"
  v-model:is-open="isAdminPopup"
  :user-no="selectedData.userNo"
  :seq="Number(seq)"
  :is-valid="false"
  @after-confirm="async () => {
    resetPwClientInfoManager(seq);
    alertMessage = messages.alert.initPasswd;
    isAlertPopup = true;
  }"
/>

핵심 포인트

  1. 부모 → 자식 전달은 항상 kebab-case (is-valid, user-no)로.
  2. 자식 props 정의는 항상 camelCase (isValid, userNo)로.
  3. v-model:is-open 같이 하이픈(-) 들어간 v-modelupdate:isOpen 이벤트와 매핑됨.
  4. TypeScript 환경에서는 interface Props로 props 타입을 명확히 지정해주는 게 안전.

👉 이렇게 해두면, IDE 자동완성 + 타입체크도 잘 되고, 협업할 때 컨벤션도 통일돼요.



isValid 값을 팝업 내부에서 변경 → 부모에게 다시 전달되는 양방향 바인딩 구조로 사용하고자 하는 경우

Vue3에서는 이럴 때 v-model을 쓰는 게 가장 깔끔합니다.


자식 컴포넌트 (CheckCustomerOtpPopup.vue)

<script setup lang="ts">
interface Props {
  phoneNm: string
  authSmsType: string
  contractNo: string
  seq: number
  isOpen: boolean
  isValid: boolean
}

const props = defineProps<Props>()

const emit = defineEmits<{
  (e: 'update:isOpen', value: boolean): void
  (e: 'update:isValid', value: boolean): void
  (e: 'after-confirm'): void
}>()

// 예: 팝업 내부에서 인증 성공 시
function handleOtpSuccess() {
  emit('update:isValid', true)  // ✅ 부모에게 전달
}
</script>

부모 컴포넌트 (예: AdminPage.vue)

<script setup lang="ts">
import { ref } from 'vue'

const isAdminPopup = ref(false)
const isOtpValid = ref(false) // ✅ 부모에서 상태 관리
</script>

<template>
  <CheckPopup
    v-if="selectedData"
    :phone-nm="seqMobile"
    v-model:is-open="adminSaveUpdatePopup"
    v-model:is-valid="isOtpValid"   <!-- ✅ 양방향 바인딩 -->
    :user-no="selectedData.contractNo"
    :seq="Number(seq)"
    @after-confirm="async () => {
      if (isOtpValid) {
        resetManager(seq);
        alertMessage = "초기화됨";
        isAlertPopup = true;
      }
    }"
  />
</template>

요약

  • v-model:is-valid 사용하면 부모에서 isOtpValid를 상태로 가질 수 있음.
  • 자식은 emit('update:isValid', 값) 호출로 부모에 값 전달.
  • 부모는 ref(false) 로 초기화 후, 인증 성공 시 true 값 자동 반영.

error: Content is protected !!