LoginSignup
44
35

More than 3 years have passed since last update.

Vue.jsでForm要素のラッパーコンポーネントを作る際の2つの方法(v-bind.sync, v-model)

Last updated at Posted at 2020-04-09

input欄を含むコンポーネントを作る際に毎回忘れるのでメモ

v-bind.syncを使う方法

v-bind.syncv-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要素)

MyInput.vue
<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>

親コンポーネント

Parent.vue
<MyInput 
  label="ラベル" 
  :value.sync="myInputValue" 
/>

オブジェクトの場合

Form要素を一括でコンポーネントにする場合などは、子コンポーネントでpropsを個々に定義しつつ
v-bind.syncに直接オブジェクトを渡すことで簡潔に実装できます。

子コンポーネント

MyForm.vue
<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>

親コンポーネント

Parent.vue
<MyFormSync 
  v-bind.sync="myFormData" 
  @submit="onSubmit"
/>

v-modelを使う方法

v-modelv-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にする必要があります。

子コンポーネント

MyInput.vue
<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>

親コンポーネント

Parent.vue
<MyInput 
  label="ラベル" 
  v-model="myInputValue"
/>

オブジェクトの場合

Form要素をまとめて一つのコンポーネントにする場合はオブジェクトを渡せると良いですよね。
その際は、プリミティブの値と同等の制約を持ちつつ、emitの際に、emit("input", { ...props.value, [key]: value });の形式で変更プロパティと、他のプロパティのマージを行い、コピーのオブジェクトをemitするようにします。

子コンポーネント(form要素)

MyForm.vue
<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>

親コンポーネント

Parent.vue
<MyForm 
  v-model="myFormValue" 
  @submit="onSubmit"
/>

どっちを使うべき?

完全に好みだと思いますが、同様の目的であればv-bind.syncの方がprops名の制約や、オブジェクト要素の場合の値のマージなどが必要ないので、使いやすいかなと思ってます。

参考

以下記事参考にさせて頂きました!!良記事ありがとうございます。

44
35
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
35