[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-case
↔camelCase
매핑을 해줍니다. - 즉, 부모에서
: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;
}"
/>
핵심 포인트
- 부모 → 자식 전달은 항상
kebab-case
(is-valid
,user-no
)로. - 자식 props 정의는 항상
camelCase
(isValid
,userNo
)로. v-model:is-open
같이 하이픈(-
) 들어간 v-model은update:isOpen
이벤트와 매핑됨.- 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
값 자동 반영.