Vue 3에서 가장 큰 변화 중 하나는 컴포넌트에 여러 개의 v-model을 동시에 사용할 수 있게 되었다는 점입니다. 이 강의에서는 modelValue와 update:modelValue를 활용하여 양방향 데이터 바인딩 컴포넌트를 설계하는 방법을 배웁니다.
기본적인 <input> 태그에는 v-model을 쉽게 사용할 수 있지만, 우리가 직접 만든 커스텀 입력 컴포넌트(예: 커스텀 드롭다운, 태그 입력기, 복합 폼)에서도 v-model을 사용하고 싶을 때가 있습니다. Vue 3에서는 이 과정이 매우 직관적이고 강력해졌습니다.
| 버전 | 동작 방식 | 한계 및 특징 |
|---|---|---|
| Vue 2 | value prop + input emit | 하나의 컴포넌트당 1개의 v-model만 허용됨. (.sync 수식어 혼용) |
| Vue 3 | modelValue prop + update:modelValue emit | 다중 v-model 바인딩 가능 (예: v-model:title, v-model:content) |
자식 컴포넌트는 부모로부터 받은 Props 데이터를 절대 직접 수정해서는 안 됩니다. 대신, 값이 변경되어야 할 때 emit('update:프롭이름', 새로운값)을 호출하여 부모에게 값을 변경해달라고 요청(이벤트)해야 합니다. 부모는 이 이벤트를 받아 자신의 상태를 업데이트하고, 변경된 값이 다시 자식에게 내려오면서 양방향 바인딩이 완성됩니다.
기본적인 <input> 태그에는 v-model을 쉽게 사용할 수 있지만, 우리가 직접 만든 커스텀 입력 컴포넌트(예: 커스텀 드롭다운, 태그 입력기, 복합 폼)에서도 v-model을 사용하고 싶을 때가 있습니다. Vue 3에서는 이 과정이 매우 직관적이고 강력해졌습니다.
| 버전 | 동작 방식 | 한계 및 특징 |
|---|---|---|
| Vue 2 | value prop + input emit | 하나의 컴포넌트당 1개의 v-model만 허용됨. (.sync 수식어 혼용) |
| Vue 3 | modelValue prop + update:modelValue emit | 다중 v-model 바인딩 가능 (예: v-model:title, v-model:content) |
자식 컴포넌트는 부모로부터 받은 Props 데이터를 절대 직접 수정해서는 안 됩니다. 대신, 값이 변경되어야 할 때 emit('update:프롭이름', 새로운값)을 호출하여 부모에게 값을 변경해달라고 요청(이벤트)해야 합니다. 부모는 이 이벤트를 받아 자신의 상태를 업데이트하고, 변경된 값이 다시 자식에게 내려오면서 양방향 바인딩이 완성됩니다.
<!-- HIDDEN_TAB -->
<div id="app"></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp, ref, defineComponent } = Vue;
const CustomForm = defineComponent({
template: `
<div class="form-container">
<h3 style="margin-top:0;">📝 복합 게시글 입력 폼 (자식)</h3>
<div class="input-group">
<label>제목 (v-model:title)</label>
<input
type="text"
:value="title"
@input="updateTitle"
placeholder="제목을 입력하세요"
/>
</div>
<div class="input-group">
<label>내용 (v-model:content)</label>
<textarea
:value="content"
@input="updateContent"
rows="4"
placeholder="내용을 입력하세요"
></textarea>
</div>
</div>
`,
props: ['title', 'content'],
emits: ['update:title', 'update:content'],
setup(props, { emit }) {
const updateTitle = (e) => {
const newValue = e.target.value;
emit('update:title', newValue);
console.log("자식 -> 부모: 제목 변경 요청 (", newValue, ")");
};
const updateContent = (e) => {
const newValue = e.target.value;
emit('update:content', newValue);
console.log("자식 -> 부모: 내용 변경 요청 (", newValue, ")");
};
return { updateTitle, updateContent };
}
});
const App = defineComponent({
components: { CustomForm },
template: `
<div>
<h2>게시판 작성 (부모)</h2>
<CustomForm
v-model:title="postTitle"
v-model:content="postContent"
/>
<div class="preview">
<h4 style="margin-top: 0;">실시간 데이터 미리보기 (부모 상태)</h4>
<p style="margin: 5px 0;"><strong>제목:</strong> {{ postTitle || '(없음)' }}</p>
<p style="margin: 0;"><strong>내용:</strong> {{ postContent || '(없음)' }}</p>
</div>
</div>
`,
setup() {
const postTitle = ref('Vue 3 다중 v-model 연습');
const postContent = ref('정말 편리하네요!');
return { postTitle, postContent };
}
});
createApp(App).mount('#app');
</script>