CodeBox CodeBox

Vueでフォームを作成①【テキストボックス編】

Vue / Nuxt
けい

本記事の内容

Vue.jsでテキストボックスを作成する方法について解説していきます。
「v-bind」・「v-model」・「emit」の練習になるので、ぜひ参考にしてください。

親コンポーネントを作成する

今回は素早く、必要な環境構築ができる「Vue-CLI」を使っています。
WebpackやTypescriptの複雑な設定をやってくれるので、非常に便利です。

Vue-CLIで作成されたApp.vueが「親コンポーネント」で、InputTextModelが「子コンポーネント」となります。
子コンポーネントには、v-modelを使ってデータをバインドさせています。

また、nameやplaceholderなどをInputTextModelにpropsとして渡しています。

<template>
  <div>
    <label>テキストボックス編(v-modelを使う)</label>
    <input-text-model
      name="InputText"
      placeholder="入力してください"
      v-model="inputContentVModel"
    ></input-text-model>
    <p>テキストボックスの結果:{{ inputContentVModel }}</p>
    <br />
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import InputTextModel from "./components/InputTextModel.vue";

// Vue.extendはtypescriptを使う場合の書き方です
export default Vue.extend({
  components: {
    "input-text-model": InputTextModel,
  },
  data() {
    return {
      inputContentVModel: "",
    };
  },
});
</script>


子コンポーネントを作成する

子コンポーネントにはフォームの設定を書いていきます。
まずは子コンポーネントのコードの全体像を見てみましょう。

<template>
  <fieldset>
    <input
      type="text"
      :name="name"
      :placeholder="placeholder"
      :value="value"
      @input="updateValue"
    />
  </fieldset>
</template>

<script lang="ts">
import Vue from "vue";

export default Vue.extend({
  props: {
    name: { type: String },
    value: { type: String },
    placeholder: { type: String },
  },
  methods: {
    updateValue(e: Event) {
      if (e.target instanceof HTMLInputElement) {
        this.$emit("input", e.target.value);
      }
    },
  },
  mounted() {
    this.$emit("input", "");
  },
});
</script>



v-bindとinput要素について理解しよう

おそらくVue初学者の方がつまづく点として、「:name = "xxx"」や「:value = "xxx"」という書き方でしょう。
まず「:」は「v-bind」の略称で、HTMLの要素の属性を動的に変えたい場合に使用します。

では属性を動的に変えるとはどういうことでしょう。通常のinputタグは下記のように表現されます。

<input type="text" name="hoge" placeholder="入力してね" 
value="sample">


タグの中の「type」や「placeholder」が属性です。
各HTMLの要素がどのような属性を持っているのかについては、MDNのドキュメントを確認するのが良いでしょう。

今のinputタグでは、nameやvalue属性の値は固定されており、常に同じ値になってしまいます。
動的に値を変えたい場合に、「v-bind」を使います。

<input type="text" v-bind:name="name" v-bind:placeholder="placeholder" 
v-bind:value="value">


毎回v-bindと書くのはめんどくさいので、省略して下記のように書くことが多いです。
:nameや:placeholderは、僕が好き勝手つけたわけではなく、inputタグが持つ属性だったのですね。

後々v-modelとv-bindを使い分ける際に、この辺の理解が必要ですので、理解しておきましょう。

<input type="text" :name="name" :placeholder="placeholder" 
:value="value">


propsについて理解しよう

"name"や"value"はpropsであり、親コンポーネントから渡された値を指します。

<input type="text" :name="name" :placeholder="placeholder" 
:value="value">


親コンポーネントからpropsを受け取った場合、必ず子コンポーネントでpropsを下記のように定義する必要があります。
propsにはtypeやdefaultなどのオプションが用意されており、propsの型を指定することができます。

export default Vue.extend({
  props: {
    name: { type: String },
    placeholder: { type: String },
    value: { type: String },
  },


親コンポーネントからは下記のようにnameやplaceholderの値を渡してあげることで、v-bindでバインドされた属性の値が変わるわけですね。
しかし、"value"というpropsは渡していないじゃないか!と思った方もいるかと思います。

これについては次の章で解説していきます。

   <input-text-model
      name="InputText"
      placeholder="入力してください"
      v-model="inputContentVModel"
    ></input-text-model>


v-modelについて理解しよう

カスタムコンポーネント(自分で作成した独自のコンポーネント)でv-modelを使う場合、v-modelは「v-bind:value & v-on:input」に分解できます。
このように複数の処理をまとめた書き方を「糖衣構文」と呼びます。もう一度親コンポーネントの記述を確認してみます。

   <input-text-model
      name="InputText"
      placeholder="入力してください"
      v-model="inputContentVModel"
    ></input-text-model>


これをv-bind:value (:value)とv-on:input (@input)で書き直すと、下記のようになります。
$eventについては次の章で解説しますが、ひとまずフォームに入力された値と理解しておいてください。

   <input-text-model
      name="InputText"
      placeholder="入力してください"
      :value="inputContentVModel"
      @input="inputContentVModel = $event"
    ></input-text-model>


こうして分解すると、:valueが存在しており、valueをpropsとして子コンポーネントに渡しているのが分かります。
カスタムコンポーネント(自分で作成した独自のコンポーネント)でv-modelを使う場合、v-modelは「v-bind:value & v-on:input」に分解できます。

$emitについて理解しよう

続いて子コンポーネントから親コンポーネントに値を渡す「$emit」について解説していきます。
子コンポーネントでは、入力されると「updateValue」というメソッドが発火するようにしています。

<template>
  <fieldset>
    <input
      type="text"
      :name="name"
      :placeholder="placeholder"
      :value="value"
      @input="updateValue"
    />
  </fieldset>
</template>


メソッドの中身は下記のようになっています。
e:Eventやif (e.target instanceof HTMLInputElement)はTypescriptを使わない場合は不要です。

export default Vue.extend({
  methods: {
    updateValue(e: Event) {
      if (e.target instanceof HTMLInputElement) {
        this.$emit("input", e.target.value);
      }
    },
  },


e.target.valueには、フォームに入力された値が入っています。
また親コンポーネントが@input = xxxとすると入力された値を取得できるようにするため、emitの中で”input”と書いています。

全体像を確認しよう

最後に親コンポーネントと子コンポーネントのコードを確認しましょう。

// 親コンポーネント
<template>
  <div>
    <label>テキストボックス編(v-modelを使う)</label>
    <input-text-model
      name="InputText"
      placeholder="入力してください"
      v-model="inputContentVModel"
    ></input-text-model>
    <p>テキストボックスの結果:{{ inputContentVModel }}</p>
    <br />
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import InputTextModel from "./components/InputTextModel.vue";

// Vue.extendはtypescriptを使う場合の書き方です
export default Vue.extend({
  components: {
    "input-text-model": InputTextModel,
  },
  data() {
    return {
      inputContentVModel: "",
    };
  },
});
</script>

// 子コンポーネント
<template>
  <fieldset>
    <input
      type="text"
      :name="name"
      :placeholder="placeholder"
      :value="value"
      @input="updateValue"
    />
  </fieldset>
</template>

<script lang="ts">
import Vue from "vue";

export default Vue.extend({
  props: {
    name: { type: String },
    value: { type: String },
    placeholder: { type: String },
  },
  methods: {
    updateValue(e: Event) {
      if (e.target instanceof HTMLInputElement) {
        this.$emit("input", e.target.value);
      }
    },
  },
  mounted() {
    this.$emit("input", "");
  },
});
</script>


ABOUT ME

けい
ベンチャーのフロントエンジニア。 主にVueとTypescriptを使っています。ライターのための文字数カウントアプリ:https://easy-count.vercel.app/