ワイのメモ帳なんやで...

主にWebプログラミングのお話をします。

Vue.js v-modelなComponent

Vue.jsを使っていると

子に渡したpropsの値を変更したら親側の値も変更されるようにしたい!

というシーンに割とよく直面する。

propsの値はそのままv-modelでは使用できないという制約もあり、回避策の知識がないと割と困ってしまうものである。(公式に書いてあるけどというツッコミは無しな!)

なので今回は子のComponentで変更された値を親に反映する方法を紹介します。

実行結果だけ知りたい人はSandboxが一番下にあるのでスクロールしてね☆

modelプロパティを指定する

Vue.js 2.2.0 からmodelプロパティが追加された。

詳しくは公式にかかれているが、正直わかりにくかったのでここでも解説する。

 

子のComponentで以下の宣言をする

export default {
  model: {
    prop: 'propData',
    event: 'input'
  },
  props: {
    propData: String
  }
}

すると親側でpropDataのbindをv-modelから行えるようになる

<template>
  <div>
    <input type="text" v-model="hoge" />
  </div>
</template>

 しかしこれだけだと子の変更を親に返却する処理が書かれていないので意味がない。

modelのeventで指定されたイベントを$emitしてやらないといけない。

以下はその返却方法を考えてみる。

 

アンチパターン watchして$emit

僕が最初に書いていた方法

mountedを通らないと親の値を子が受け取れないし、方法ですらない。

 皆は真似しないでね!

<template>
  <div>
    <label>Sample1&nbsp;</label>
    <input type="text" v-model="inputData" />
  </div>
</template>

<script>
export default {
  name: 'Sample1',
  model: {
    prop: 'propData',
    event: 'input'
  },
  props: {
    propData: String
  },
  data () {
    return {
      inputData: null
    }
  },
  mounted () {
    if (this.propData) {
      this.inputData = this.propData
    }
  },
  watch: {
    inputData (nextValue) {
      this.$emit('input', nextValue)
    }
  }
}
</script>

 

computedを利用する

いやぁ、最初に見たときは感動したね。

<template>
  <div>
    <label>Sample2&nbsp;</label>
    <input type="text" v-model="inputData" />
  </div>
</template>

<script>
export default {
  name: 'Sample2',
  model: {
    prop: 'propData',
    event: 'input'
  },
  props: {
    propData: String
  },
  computed: {
    inputData: {
      get () {
        return this.propData
      },
      set (value) {
        this.$emit('input', value)
      }
    }
  }
}
</script>

porpsを直接v-modelに指定できないから一旦computedを間に通してしまおうという魂胆。

v-modelの変更はset関数に入ってくるので、その値を親に返却。

美しく且つクールな書き方。

 

余談 v-modelを複数指定したい場合

v-modelは一つのパラメータしか指定できない。

複数の値を指定したい場合は Vue.js 2.3.0 以降でのみ使用可能なsync修飾子を使用する。

 

<template>
  <div id="app">
    <sample3 :foo.sync="sample3_1" :bar.sync="sample3_2" />
  </div>
</template>

 

<template>
  <div>
    <label>Sample3&nbsp;</label>
    <input type="text" v-model="inputData1" />
    <input type="text" v-model="inputData2" class="ml-1"/>
  </div>
</template>

<script>
export default {
  name: 'Sample3',
  props: {
    foo: String,
    bar: String
  },
  computed: {
    inputData1: {
      get () {
        return this.foo
      },
      set (value) {
        this.$emit('update:foo', value)
      }
    },
    inputData2: {
      get () {
        return this.bar
      },
      set (value) {
        this.$emit('update:bar', value)
      }
    }
  }
}
</script>

<style scoped>
.ml-1 {
  margin-left: .1rem;
}
</style>

v-bindで.syncを指定すると 子側の$emitでupdateイベントに変数名を追記することで親で指定されたプロパティにたいして値の更新を行えるようになる。

 

 

各実行結果はこちら

 

ということで今回はここらへんで失礼します。