LoginSignup
6
5

More than 3 years have passed since last update.

【Vue】v-modelを完全理解!v-modelの基本動作と応用パターン。混乱するパターンのまとめと対処法。

Last updated at Posted at 2020-11-17

vueで多用するv-modelの理解を深めるため、処理をより基本的なv-bind(:)とv-on(@)で記述してみる。

また、v-model使用時のよくある勘違いについてパターン別にまとめ。

目次

  1. v-modelでできること
  2. v-modelを使った記述
  3. v-modelを使わない書き方(1)
  4. v-modelを使わない書き方(2)
  5. v-modelを使った親子間のデータやりとり
    1. 親から子にデータを渡すのみ
    2. 子のみでデータ操作
    3. 親と子でデータ操作
    4. v-modelとcomputedを使わない方法(sync)
  6. よくある混乱ポイント(親子でのデータ連動)
    1. 親と子の双方にv-modelが必要なのか?
    2. 親でvalueが使われてないのに、なんで子のpropsにvalueが出てくるのか?
    3. 子のv-modelで指定する値に親の変数名は使えないのか?
    4. なぜcomputedが必要なのか?
    5. エラー(Property or method "変数名" is not defined)
    6. エラー(Avoid mutating a prop directly)


v-modelでできること

inputタグなどのテキスト入力や、セレクトボックスの選択内容の変更に合わせて変数を変更することができる。

ブラウザの画面表示と裏側のデータを双方向でつなげるため、双方向バインディングと呼ぶ。

▼双方向バインディングのイメージ

  • 画面上のUI操作で入力値や選択項目を変更 → 変数も変わる。
  • 変数を変える → 画面上の表示が変わる。


例: inputタグで使った場合

inputタグの例でみると、inputタグの表示内容であるvalue属性に変数を指定。

inputイベントで入力内容の変更を取得しその変数に代入する操作となる。

image.png

↓ 内容を変更

image.png

下部に表示している変数の中身も変化する。


v-modelを使った記述

属性にv-modelを記載し変数を指定するだけ。実にシンプル。

template
<input type="text" v-model="msg">

v-modelの裏側にはvalue属性が隠れている。このため、上記はinputタグのvalue属性として変数msgを指定したという意味になる。

またinputタグ内の変更を検知する@inputも隠れている。次章のv-modelを使わない書き方を見るとわかりやすい。



▼フルコード

fullコード
<template>
<div>
    <input type="text" v-model="msg">

    <p>・msg: {{msg}}</p>
  </div>
</template>

<script>
export default {
  data(){
    return{
      msg: "初期メッセージ"
    }
  }
}
</script>


v-modelを使わない書き方(1)

v-modelを使わずに書く場合は、:value@inputを使う。

template
<input type="text"
      :value="msg"
      @input="textChange">

入力内容に変化があったらイベントtextChangeが発火。イベントはscriptのmethodsに記載。

script
methods:{
    textChange(event){
      this.msg = event.target.value
    }
  }

変更内容の値、event.target.valueを取得し、変数に代入する。

・event内容の受け渡し
タグのイベントハンドラで引数を指定しない場合は、メソッドの第一引数で$eventが渡される。

▼フルコード

v-modelを使わない
<template>
<div>
    <input type="text"
      :value="msg"
      @input="textChange">

    <p>・msg: {{msg}}</p>
  </div>
</template>

<script>
export default {
  data(){
    return{
      msg: "初期メッセージ"
    }
  },
  methods:{
    textChange(event){
      this.msg = event.target.value
    }
  }

}
</script>


v-modelを使わない書き方(2)

上記はメソッドを用意したが、タグ内で変数に代入することも可能。

<input type="text"
      :value="msg"
      @input="mas = $event.target.value">

この場合、methodsの表記は不要になる。


フルコード
<template>
<div>
    <input type="text"
      :value="msg"
      @input="mas = $event.target.value">

    <p>・msg: {{msg}}</p>
  </div>
</template>

<script>
export default {
  data(){
    return{
      msg: "初期メッセージ"
    }
  }
}
</script>

v-modelの裏側では:value@input属性が隠れていることを知るのが重要。


参考(inputとchange)

ちなみに、inputをchangeに変更すると、テキストを編集後、enterキーをクリック後にメソッドが発火する。

<input type="text"
      :value="msg"
      @change="mas = $event.target.value">

このようにイベントで処理結果が異なる。

v-modelはこの辺が賢く、

・テキストボックスやtextareaの場合は、@inputが裏で処理されている形となり、

・button関連、チェックボックスやラジオボタン、セレクトボックスの場合は@changeが裏で処理される形となる。


v-modelを使った親子間のデータやりとり

応用編として、親子間でデータを受けわたす方法を確認する。この場合もv-modelが有効。

大きく、以下3つのパターンを考える。

(1) 親から子にデータを渡すのみ
親から子にデータを渡すのみ。変更内容を親に戻す必要がない場合。

(2) 子のみでデータ操作
親のデータを子に渡し、子でデータを編集し、その結果を親に表示する。

(3) 親と子でデータ操作
親のデータを子に渡し、親と子の双方でデータをいじれるようにする。


(1) 親から子にデータを渡すのみ

親子それぞれのテンプレートの役割を以下とする。

・親
- dataを定義。
- 子テンプレートを読み込み表示する。

・子
- テキストエリアをもつ。
- テキストエリアには親のデータを表示。
- ファイル名は「SyncChiled.vue」。


画面表示イメージ
image.png
親のコード
<template>
  <div>
    <!-- 子テンプレートを呼び出し、プロパティvalueで変数msgを共有 -->
    <SyncChiled v-model="msg" />

    ・親で定義した変数: {{msg}}
  </div>
</template>

<script>
import SyncChiled from "./SyncChiled" 

export default {
  components:{
    SyncChiled //子テンプレをタグとして使えるようにする
  },
  data(){
    return{
      msg: "初期メッセージ"
    }
  }
}
</script>

▼point

<子テンプレ v-model="msg" />の役割

  • 子にv-modelでデータを渡す。
  • データは属性valueとして渡す。(変数名で渡すのではない!
  • @inputも設定してあることになるが、今回は使っていない。

@inputを使ってないので、v-modelではなく、:valueでも同じ動作になる。(v-modelの:value機能のみ使っている)


子(SyncChiled.vue)のコード
<template>
  <!-- 渡すテンプレート -->
  <div>
    <!-- 属性valueに、親から受け取った属性(を代入した変数)を代入 -->
    <input type="text" :value="value">
  </div>
</template>

<script>
export default {
  props:{
    value: {type:String}
  }
}
</script>

▼point

props:{ value }
<:value="value">の理解が重要!

  • props:{ value }のprops:

    • 親から受け取った属性を変数として使えるようにする
    • 定義するのは親側でv-model:属性名で指定したもの
    • 型や必要性、親から渡されない場合の初期値などの設定も可能
  • props:{ value }のvalue

    • v-modelの裏側は:valueとなっているため、親のv-modelで指定した値をvalueとして受け取っている。
  • <:value="value">の:value=

    • inputタグのvalue属性にイコール以下を設定しますという意味
    • :をつけることで、変数を指定できる。
  • <:value="value">の"value"

    • propsで定義した変数valueをinputタグの表示内容として使うという意味。



v-modelをややこしくしてるのはこのvalue地獄だと思う。。

それぞれの違いを理解することがとても大事なので、一つづつ確認していくことが重要。


(2) 子のみでデータ操作

子でデータを受け取るのみとの違いは、computedプロパティでgetterとsetterを設定すること。

親子それぞれのテンプレートの役割を以下とする。

・親
- dataを定義。
- 子テンプレートを読み込み表示する。
- 子で操作した変更内容が反映されるか確認用に変数を表示する。

・子
- テキストエリアをもつ。
- テキストエリアには親のデータを表示。
- データが変更されたら親に出力する。
- ファイル名は「SyncChiled.vue」。

画面のイメージ
image.png


親のコード
<template>
  <div>
    <!-- 子テンプレートを呼び出し、プロパティvalueで変数msgを共有 -->
    <SyncChiled v-model ="msg" />

    <!-- 親で定義しているデータが変更されるかの確認用 -->
    ・変数msgを表示: {{msg}}
  </div>
</template>

<script>
//子テンプレートをインポート
import SyncChiled from "./SyncChiled" 

export default {
  components:{
    //子をコンポーネントとして宣言(タグとして使えるようにする)
    SyncChiled
  },
  data(){
    return{
      //定義したデータ
      msg: "初期メッセージ"
    }
  }
}
</script>

親のコードは変更なし。

▼point

<子テンプレ v-model="msg" />これが重要

  • 子にv-modelでデータを渡す。
  • データは属性valueとして渡す。(変数名じゃない!
  • @inputも設定してあることになる。


子(SyncChiled.vue)のコード
<template>
 <!-- 渡すテンプレート -->
  <div>
  <!-- 親に戻すデータをv-modelで指定 -->
    <input type="text" v-model="chiledMsg">
  </div>
</template>

<script>
export default {
  props:{
    //親から受け取った属性
    value: {type:String}
  },
  computed: {
    //受け取った属性をデータに変換する
    chiledMsg: {
      get() {
        return this.value;
      },
      //変更があったら親に変更内容を渡す
      set(newVal) {
        this.$emit('input', newVal);
      }
    }
  }
}
</script>

▼point

<v-model="chiledMsg" />
computed: {chiledMsg:{get, set}}の2つが重要。

  • 子の変更内容を検知して、そのデータを取得するため、v-modelを設置する。
  • inputタグに親から受け取ったデータを表示し、変更があった場合にその内容を取得して親に戻すために、computedでgetterとsetterを用意する。
  • 親にデータを渡すには$emitを使う。
    • 書き方:this.$emit('親のイベント名', 渡すデータ)
    • 親のv-modelが@inputイベントも担っている。
  • v-modelの変数名は親の変数と混乱しないように名前を変えている。(同じでも動く)


(3) 親と子でデータ操作

親と子の双方で変数を変更できるようにするには、親のinputタグにも同じv-modelを設置すればいい。

<template>
  <div>
    <!-- 子テンプレートを呼び出し、プロパティvalueで変数msgを共有 -->
    <SyncChiled v-model="msg" />

    <!-- 親の要素 -->
    <input type="text" v-model="msg">
  </div>
</template>

▼確認用画面イメージ
上側が子の要素。下側が親で設定したinputタグ。
image.png

※v-modelの注意点

  • v-modelは一つのタグに一つしか使えない。
  • 1つのタグ内で複数使いたい場合は、「async」か、「@ inputと:任意の属性」を使う。
  • 1つのテンプレートで使えるv-modelで指定できる変数は一つ。


v-modelとcomputedを使わない方法(sync)

.syncを使うと、v-modelやcomputedを使わずに親子間でデータの受け渡し・変更ができる。

syncの使い方はこちら


よくある混乱ポイント

親と子の双方にv-modelが必要なの?

双方にv-modelを設置することが必須

▽親のv-modelの役割

  • 親のv-modelの役割は①子にデータを渡す(value属性として)、②子からデータを受け取る(@ input)としての2役を担っている。

  • 例えば、v-modelを:valueに変更すると、親から子にデータは渡せるが、子からデータを受け取れない。

▽子のv-modelの役割

  • 子のv-modelの役割は①属性valueに変数を代入する(親から受け取ったデータの表示)。②変更内容を取得するの2点になる。

  • 親にデータを渡す操作は、②変更内容を取得後の操作として別途設定している。(※v-model自体が親にデータを渡しているわけではない

  • 親からのデータ表示のみで、変更する必要がない場合はv-model:value="value"に変更すればOK。


親でvalueが使われてないのに、なんで子のpropsにvalueが出てくるの?

inputタグ内のv-modelの裏側が:valueとなっているため。
つまり、親にv-modelがあれば、子のpropsにはvalueが宣言される。

逆に、子のpropsにvalueが定義してあれば、親のタグ内にv-model="":value=""があるということ。


子のv-modelで指定する値に親の変数名は使えないの?

使えます。

親で定義した変数は親の中で有効。子で定義した変数は子の中で有効。

それぞれ異なる変数なので(内容は同じだが変数自体はイコールではない)、子では別の変数名を設定することが多い。

▼例
親で次のようなv-modelが設定されている場合、
<子テンプレ v-model="msg" />

以下2つの子テンプレートは同じ処理になる。

子1(親と異なる変数名を使用)
<template>
  <!-- 渡すテンプレート -->
  <div>
    <!-- 親に戻すデータをv-modelで指定 -->
    <input type="text" v-model="chiledMsg">
  </div>
</template>

<script>
export default {
  props:{
    value: {type:String}
  },
  computed: {
    chiledMsg: {
      get() {
        return this.value;
      },
      set(newVal) {
        this.$emit('input', newVal);
      }
    }
  }
}
</script>
子2(親と同じ変数名を使用)
<template>
  <!-- 渡すテンプレート -->
  <div>
    <!-- 親に戻すデータをv-modelで指定 -->
    <input type="text" v-model="msg">
  </div>
</template>

<script>
export default {
  props:{
    value: {type:String}
  },
  computed: {
    msg: {
      get() {
        return this.value;
      },
      set(newVal) {
        this.$emit('input', newVal);
      }
    }
  }
}
</script>

ポイントは親の<子テンプレ v-model="msg" />で子に渡しているのが、変数msgではなく、v-modelの裏に隠されたvalue属性だということ。

つまり、子の中にはmsgが登場していないので、新たに宣言しても重複にならない。


なぜcomputedが必要なのか?

computedでgetterとsetterを設定する理由は、これがない場合を考えるとわかりやすい。

computedがないと、テキスト変更時にpropsで受け取ったvalueを直接変更することになる

しかし、valueは子のテンプレートに定義しておらず、親のデータとなるため、直接編集できない(エラーが出る)


▽親のデータを変更するための処理

変更を可能にするには、
(1)子で変更を検知

(2)親に変更内容をイベント(属性)として飛ばす

(3)親がイベントを実行し変数を変更する。

という手順を踏む必要がある。この処理を記述するためにcomputedが必要。

この親へのデータ送信処理を記述するのがset()。

同時にこの変数には、親から受け取ったデータの内容を代入する必要がある。この処理を行うのがget()。

get()とset()はセットで記述しないと目的の役割を果たさない。このためどちらかがないとエラーが発生する。

▽setを記述しない場合のエラー

・エラー内容
[Vue warn]: Getter is missing for computed property "変数名".

▽getを記述しない場合のエラー

・エラー内容
[Vue warn]: Computed property "変数名" was assigned to but it has no setter.

変数名にはcomputedで指定した名前が入る


エラー(Property or method "変数名" is not defined)

以下のような長めのエラーが発生する場合もある。

これは、computedを設定せずに、親から受け取った(propsで定義した)データを直接変更しようとしたために発生するエラー。

computedでgetterとsetterを記述することで解消できる。

[Vue warn]: Property or method "変数名" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.

「指定した変数名が宣言されてない。dataオプションで定義が必要です」と教えてくれている。

どういう場合に発生しやすいか?

単純に変数を宣言してない場合に発生するが、特にv-modelでは、

親のv-modelで指定した変数を、子のv-modelに記述して、変数同士をつないだ!という勘違いをした場合に発生する。

親から子に渡すのは、親で指定した変数ではなく、v-modelの裏側に隠れたvalue属性の方。

子のpropsで宣言するのも「親からvalue属性を受け取り変数として使用します」ということなので、子の中に親で使っている変数は定義されていない。


エラー(Avoid mutating a prop directly)

長めのエラーでAvoid mutating a prop directlyが発生することもある。

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "変数名"

これは、親で定義した変数を直接変更しようとした場合に発生する

子テンプレで、propsでvalueを親から受け取り、v-model="value"としてしまった場合など。

エラー内容が実に端的にその内容を表している。



▼エラー内容(日本語ver)

  • (親から受け取ったデータ)propを直接変更しちゃダメ。親のテンプレ読み込んだ時にデータ上書きしちゃうよ。
  • propのデータじゃなくて、dataかcomputedを指定してね。
  • 変更しようとしてる変数はこれだよ:"(propsの)変数名"。

親切、、だけど文章が長すぎて読む気なくすパターン。エラー内容は1文づつちゃんと見よう。。

6
5
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
6
5