modelValue prop과 update:modelValue emit을 defineModel 한 줄로 줄이기
문제 발생
v-model을 지원하는 입력 컴포넌트를 만들 때마다 같은 배선을 반복하고 있었습니다.
<script setup>
const props = defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]);
</script>
<template>
<input
:value="props.modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
</template>컴포넌트가 늘어날수록 update:modelValue 오타로 동작하지 않는 일이 반복됐습니다.
원인 분석
컴포넌트의 v-model은 문법 설탕입니다. 부모의 v-model="foo"는 이렇게 펼쳐집니다.
<Child
:modelValue="foo"
@update:modelValue="$event => (foo = $event)"
/>그래서 자식은 modelValue라는 prop과 update:modelValue라는 emit을 선언해야 했습니다. 위 코드는 그 규약을 사람이 손으로 다시 적은 것이고, 문자열이라 오타가 컴파일 시점에 잡히지 않습니다.
Vue 3.4에서 defineModel() 이 안정화되면서 이 배선이 매크로 한 줄로 줄었습니다. 컴파일러가 modelValue prop과 update:modelValue emit으로 펼쳐 주고, 반환값은 양방향으로 연결된 ref입니다. .value가 부모의 값과 동기화되고, 자식이 바꾸면 부모까지 반영됩니다.
해결 방안
- 한 줄로 대체합니다.
<script setup>
const model = defineModel();
function increment() {
model.value++; // 부모 값까지 갱신
}
</script>
<template>
<input v-model="model" />
<button @click="increment">증가</button>
</template>- 이름 있는 모델은 인자로 넘깁니다. 부모의
v-model:title="bookTitle"에 대응합니다.
const title = defineModel("title"); // prop: title, emit: update:title
const firstName = defineModel("firstName");
const lastName = defineModel("lastName");- prop 옵션은 두 번째 인자입니다.
const model = defineModel({ required: true });
const title = defineModel("title", { required: true });default에는 함정이 있습니다. 공식 문서가 경고합니다 —default를 줬는데 부모가 값을 주지 않으면 부모와 자식이 어긋납니다.
<!-- 자식 -->
<script setup>
const model = defineModel({ default: 1 });
</script>
<!-- 부모 -->
<script setup>
const myRef = ref(); // undefined
</script>
<template><Child v-model="myRef" /></template>결과는 부모 myRef는 undefined, 자식 model은 1입니다. 부모에서 초기값을 주거나, 기본값을 자식에 두지 않는 편이 안전합니다.
- 수정자(modifier)는 구조 분해로 받습니다.
v-model.capitalize="text"같은 사용자 정의 수정자를get/set으로 처리할 수 있습니다.
const [model, modifiers] = defineModel({
set(value) {
if (modifiers.capitalize) {
return value.charAt(0).toUpperCase() + value.slice(1);
}
return value;
},
});이름 있는 모델도 같은 형태입니다.
const [firstName, firstNameModifiers] = defineModel("firstName");- 3.4 미만이면 기존 방식을 유지합니다. 매크로이므로 폴리필이 없습니다. 버전을 올릴 수 없다면
computed의get/set으로 감싸는 편이 오타에 강합니다.
const model = computed({
get: () => props.modelValue,
set: (value) => emit("update:modelValue", value),
});
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.