input欄を含むコンポーネントを作る際に毎回忘れるのでメモ
v-bind.syncを使う方法
v-bind.sync
はv-bind:hoge
, v-on:update:hoge="hoge = $event"
のシンタックスシュガーです。
これを意識して子コンポーネントでイベントを発行することで、Form要素を内包するコンポーネントを簡潔に実装できます。
<!-- <MyInput v-bind:hoge.sync="hoge">は以下と同義 -->
<MyInput
:hoge="hoge"
v-on:update:hoge="hoge = $event"
>
プリミティブな値の場合
input要素単体のコンポーネントなどの場合は、単純にコンポーネント内での変更感知のemit名をupdate:props名
として値を発行すればOKです。
子コンポーネント(input要素)
<template>
<label
>{{ label }}
<input type="text" :value="value" @input="updateValue($event.target.value)" />
</label>
</template>
<script lang="ts">
import { defineComponent } from "@vue/composition-api";
export default defineComponent({
props: {
label: {
require: true,
type: String
},
value: {
require: true,
type: String
}
},
setup(_, { emit }) {
const updateValue = (value: string) => emit("update:value", value);
return {
updateValue
};
}
});
</script>
親コンポーネント
<MyInput
label="ラベル"
:value.sync="myInputValue"
/>
オブジェクトの場合
Form要素を一括でコンポーネントにする場合などは、子コンポーネントでpropsを個々に定義しつつ
v-bind.sync
に直接オブジェクトを渡すことで簡潔に実装できます。
子コンポーネント
<template>
<form>
<label>
title
<input
type="text"
:value="title"
@input="updateValue('title', $event.target.value)"
/>
</label>
<label>
content
<input
type="textarea"
:value="content"
@input="updateValue('content', $event.target.value)"
/>
</label>
<button @click="onSubmit">submit</button>
</form>
</template>
<script lang="ts">
import { defineComponent } from "@vue/composition-api";
export default defineComponent({
props: {
title: {
require: true,
type: String
},
content: {
require: true,
type: String
}
},
setup(props, { emit }) {
const updateValue = (key: string, value: string) => {
emit(`update:${key}`, value);
};
const onSubmit = () => emit("submit");
return {
onSubmit,
updateValue
};
}
});
</script>
親コンポーネント
<MyFormSync
v-bind.sync="myFormData"
@submit="onSubmit"
/>
v-modelを使う方法
v-model
はv-bind:value="hoge"
, @input="hoge = $event.target.value"
のシンタックスシュガーです。
<!-- <input v-model=hoge>は以下と同義 -->
<MyInput
:value="name"
@input="name = $event.target.value"
>
プリミティブな値の場合
v-modelをカスタムコンポーネントで使う場合は、子コンポーネントが受け取るpropsを必ずvalue
にする必要があります。また、$emitの際のイベント名は必ずinput
にする必要があります。
子コンポーネント
<template>
<label
>{{ label }}
<input type="text" :value="value" @input="updateValue($event.target.value)" />
</label>
</template>
<script lang="ts">
import { defineComponent } from "@vue/composition-api";
export default defineComponent({
props: {
label: {
require: true,
type: String
},
value: {
require: true,
type: String
}
},
setup(_, { emit }) {
const updateValue = (value: string) => emit("input", value);
return {
updateValue
};
}
});
</script>
親コンポーネント
<MyInput
label="ラベル"
v-model="myInputValue"
/>
オブジェクトの場合
Form要素をまとめて一つのコンポーネントにする場合はオブジェクトを渡せると良いですよね。
その際は、プリミティブの値と同等の制約を持ちつつ、emitの際に、emit("input", { ...props.value, [key]: value });
の形式で変更プロパティと、他のプロパティのマージを行い、コピーのオブジェクトをemitするようにします。
子コンポーネント(form要素)
<template>
<form>
<label>
title
<input
type="text"
:value="value.title"
@input="updateValue('title', $event.target.value)"
/>
</label>
<label>
content
<input
type="textarea"
:value="value.content"
@input="updateValue('content', $event.target.value)"
/>
</label>
<button @click="onSubmit">submit</button>
</form>
</template>
<script lang="ts">
import { defineComponent } from "@vue/composition-api";
type FormData = {
title: string;
content: string;
};
export default defineComponent({
props: {
value: {
require: true,
type: Object as () => FormData
}
},
setup(props, { emit }) {
const updateValue = (key: string, value: string) => {
emit("input", { ...props.value, [key]: value });
};
const onSubmit = () => emit("submit");
return {
onSubmit,
updateValue
};
}
});
</script>
親コンポーネント
<MyForm
v-model="myFormValue"
@submit="onSubmit"
/>
どっちを使うべき?
完全に好みだと思いますが、同様の目的であればv-bind.sync
の方がprops名の制約や、オブジェクト要素の場合の値のマージなどが必要ないので、使いやすいかなと思ってます。
参考
以下記事参考にさせて頂きました!!良記事ありがとうございます。