Vue 2, Vue 3によってコンポーネントの作成方法が異なるため両方の方法で記述しています。

vue.jsでは入力フォーム内を構成する各要素をコンポーネント化し再利用することでアプリケーション内で統一したデザインの入力フォームを作成することができます。コンポーネント内の要素でCSSを適用するため、コンポーネントを利用する親コンポーネントからCSSを設定する必要がありません。コンポーネントは独立しているため他のプロジェクトで再利用することも可能です。またデザインを変更したい場合は1つのコンポーネントのCSSを変更するだけで変更作業が完了し個別にCSSを適用している場合に比べて作業効率は各段に向上します。

入力フォーム内を構成する最小単位の1つとしてinput要素があります。最近ではAtomic Designを取り入れてアプリケーションを構築することもあり、input要素のコンポーネント化の方法を理解しておくことは非常に重要です。

コンポーネント化の前にVue.jsの入力フォームの利用方法を理解した場合は下記の文書がおすすめです。

v-modelディレクティブとは

input要素などの入力フォームの要素にv-modelディレクティブを設定することで要素に入力した値とVue.jsで定義したデータプロパティで双方向のデータバインディングが行われます。

データバインディングが行われるとinput要素で入力した値がそのままデータプロパティに設定されます。Vue.jsで入力フォームを作成している人にとっては見慣れた書式だと思います。


<input v-model="message" />
//略
//データプロパティのmessageの定義忘れずに
data() {
  return {
    message: "",
  }
}
input要素
input要素

このv-modelですが実は下記のように書き換えることができます。$event.targetはinput要素そのものです。$event.target.valueによりinput要素に入力した値をvalueで取得しています。


<input v-bind:value="message" v-on:input="message = $event.target.value" />
$eventという名前の変数なので$を省略するとエラーとなります。

v-modelを使った書式はsyntax sugar(糖衣構文)と呼ばれv-bind, v-onを使って記述方法をシンプルにわかりやすくしています。

v-bindとv-onを短縮形にすると以下のようになります。


<input :value="message" @input="message = $event.target.value" />
v-modelから:valueと@inputを使って記述に変更しても動作が変わらないことを確認してください。

@inputイベントとは

@inputはinput要素に文字を入力される度に発火されるイベントです。日本語の変換が確定していなくてもイベントは発火されます。文字を入力する度にevent.target.valueの値がmessageプロパティに設定されます。

また以下のように@inputイベントにメソッドを設定して値の設定を行っても処理の動作は変わりません。


<input :value="message" @input="inputValue" />
//略
        methods:{
            inputValue(){
                this.message = event.target.value;
            }
        }
@input要素の中では$eventでしたが、メソッドの場合は$は必要ありません。

input要素のコンポーネントの作成

ここまでの説明でv-modelはv-bindと@inputイベントで記述できることがわかりました。その記述を利用することでinput要素のコンポーネント化を行うことができます。

input要素のコンポーネントのBaseInput.vueファイルを作成します。input要素をコンポーネント化する目的には”統一されたデザインの入力フォームの作成”があるのでclass(ここではform-inputを設定)を設定しています。それぞれのアプリケーションのデザインに合わせたclassを設定してください。input要素のデザインを変更したい場合はこclassを設定することでこのコンポーネントを利用しているすべてのフォームで変更が反映されます。

Vue 2の場合

設定方法についてはマニュアルのhttps://vuejs.org/v2/guide/components.html#Using-v-model-on-Componentsに記載されています。


<template>
  <input 
    class="form-input" 
    :value="value" 
    @input="$emit('input', $event.target.value)"
  >
</template>

<script>
  export default {
    props: ['value'],
  }
</script>

BaseInput.vueのコンポーネントは親コンポーネントからpropsでvalueを受け取り、input要素に入力した値は$emitを利用して親コンポーネントにデータを渡しています。$emitを利用して親のデータを渡すというのがポイントになります。

Vue.jsでは親コンポーネントから子にデータを渡したい場合はprops、子コンポーネントから親にデータを渡したい場合は$emitを利用します。

親側のコンポーネントでは@inputを利用して子コンポーネントからemitされたinputイベントを取得してその値をmessageプロパティに設定しています。emitの引数で指定した値は$eventでアクセスすることができます。


<text-input :value="message" @input="message = $event" />
emitで設定しているinputという名前を別の名前にしても動作します。その場合は親側で受け取るイベント名も忘れずに変更を行ってください。

上記の記述は下記のようにsyntax sugar(糖衣構文)であるv-modelを利用して記述することが可能です。


<text-input v-model="message" />

一度コンポーネントを作成すればinputタグからbase-inputタグに変更する必要はありますが、inputタグに対して個別にCSSを適用することなく統一したデザインの入力フォームを作成することができます。

Vue 3の場合

設定方法についてはマニュアルのhttps://v3.vuejs.org/guide/component-basics.html#using-v-model-on-componentsに記載されています。

Vue2の場合はイベント名がinputでしたがVue3ではupdate:modelValueとなります。またVue3ではイベントもemitsでpropsのように設定を行います。イベント名前はVue2と異なりますがemitを利用して親に入力した値を渡します。


<template>
    <input
      class="input-form"
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    >
</template>

<script>
  export default {
      props: ['modelValue'],
      emits:['update:modelValue']
  }

親側でv-modelを利用する場合はpropsはmodelValueまたイベント名はupdate:modelValueという名前を設定してください。v-modelを利用する場合はinput要素からのイベントの名前がupdate:modelValueでpropsがmodalValueが初期値であるためです。もしmodelValueという初期値を変更したい場合はmodelValueを変更したい名前に変更(ここではmodelValueからvalue)します。


<template>
    <input
      class="input-form"
      :value="value"
      @input="$emit('update:valule', $event.target.value)"
    >
</template>

<script>
  export default {
      props: ['value'],
      emits:['update:value']
  }

親側でv-modalの後ろにvalueをv-modal:valueのように指定をする必要があります。

親側では子コンポーネントかpropsはBaseInput.vue側で設定したmodel-valueを使い受け取るイベント名はupdate:model-valueと設定します。


  <base-input 
    :modelValue="message" 
    @update:modelValue="message = $event"  
  />

上記の記述は下記のようにsyntax sugar(糖衣構文)であるv-modelを利用して記述することが可能です。


<text-input v-model="message" />

一度コンポーネントを作成すればinputタグからbase-inputタグに変更する必要はありますが、inputタグに対して個別にCSSを適用することなく統一したデザインの入力フォームを作成することができます。

$attrsでinputの属性を渡す

input要素のtype, name, placeholder属性をBaseInputコンポーネントに渡したい時はインスタンスプロパティの$attrsを利用することができます。

texxt-inputタグにname, type, placeholder属性を設定します。$attrsについてはVue3, Vue2でも同じように利用することができます。


<base-input v-model="message"  name="message" type="text" placeholder="メッセージを入力してください" />

$attrsから本当に設定した属性が取得できるのか確認を行います。


<template>
  {{ $attrs }}
  <input
    class="input-form"
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  >
</template>

<script>
  export default {
      props: ['modelValue'],
      emits:['update:modelValue']
  }
</script>

ブラウザ上にはbase-iputタグで設定した属性が表示されます。


{ "type": "text", "name": "message", "placeholder": "メッセージを入力してください" }

これらの属性をBaseInput.vueコンポーネントのinput要素に設定するためにv-bindを利用します。v-bindを利用するとすべてのpropsを一度に親コンポーネントから子コンポーネントに渡すことができます。


<input
  class="input-form"
  :value="modelValue"
  @input="$emit('update:modelValue', $event.target.value)"
  v-bind="$attrs"
>

input要素のname属性にmessage, type属性にtext, placeholderにメッセージを入力してくださいと設定されたinputが表示されます。

$attrsを設定後に反映されることを確認
$attrsを設定後に反映されることを確認

v-bindを利用して下記のようにコードを記述することもできます。


<template>
  <input
    class="input-form"
    :value="modelValue"
    v-bind="{
      ...$attrs,
      onInput : ($event) => $emit('update:modelValue', $event.target.value) 
      }"
  >
</template>

select要素のコンポーネント化の方法

select要素もinput要素のようにコンポーネント化することが可能です。select要素の場合はinput要素の場合と異なり、selectで選択するoptionをpropsで渡す必要があります。

コンポーネント化の前のv-modelを利用したselect要素を確認します。選択するオプションにはcarsの配列を準備し、v-forを利用して展開しています。


<template>
  <div>
    <select v-model="car" >
      <option 
        v-for="car in cars" 
        :key="car" 
        :value="car"
      >
        {{car}}
      </option>
    </select>
    <p>{{ car }}</p>
  </div>
</template>
<script>
export default {
  components:{
  },
  data() {
    return {
      car:'audi',
      cars: ['volvo','saab','mercedes','audi'],
    }
  },
}
</script>

選択したオプションが表示されるように{{ car }}でブラウザ上に表示させています。select要素で項目を選択すると選択した車が表示されます。

select要素が表示
select要素が表示

select要素をコンポーネント化するためBaseSelect.vueファイルを作成します。

つづく