blog logo

Vue.jsのAvoid mutating a prop directly since ...エラーがなぜ発生するのかを解説

thumnail
2020-02-24

Vue.jsを書いていて、子コンポーネントから親コンポーネントの値を更新しようとした時、次のエラーが検証ツールのconsoleに表示された。

[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. So let's keep this in mind, mutating props is a no-go in Vue. js

なぜこのエラーが発生したのか、このエラーを回避するにはどうしたら良いのかを調べたので備忘録としてまとめておく。

初心者がハマるVue.jsの仕様

子コンポーネントで親コンポーネントの値を直接更新できない

Vue.jsを書き始めたばかりのエンジニアは恐らく次の警告文に一度は遭遇し、頭を悩ませるをと思う。

[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. So let's keep this in mind, mutating props is a no-go in Vue. js

警告文をざっくり解釈してみると、親コンポーネントから子コンポーネントに渡した値、すなわちpropsを子コンポーネント内で直接更新するのを避けろと書かれている。せっかくなのでこのエラーを再現するために次のコードを用意した。まずは親コンポーネントのコード。

    // parent.vue
    
    <template>
      <div>
        <child :value="value" />
      </div>
    </template>
    
    <script>
    import Child from './components/child'
    export default {
      components: {
        Child,
      },
      data(){
        return {
          value: 'hoge',
        }
      }
    }
    </script>

次に子コンポーネントのコード。

    // ./components/child.vue
    
    <template>
      <div>
        <input v-model="value">
      </div>
    </template>
    
    <script>
    export default {
      props: {
        value: {
          type: String,
          required: true,
          default: ''
        }
      }
    }
    </script>

ここで子コンポーネントが提供するinput要素に対して値を入力すると

[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: "value" found in ---> at src/components/child.vue at src/App.vue

のエラーが検証ツールのconsoleに出力される。これは親コンポーネントから渡された値を子コンポーネントで書き換えてはいけないというVue.jsの仕様が存在するため、このようなエラーが表示されるのだ。今回のコードで言うと子コンポーネントのinput要素のv-modelでpropsを直接更新していることが原因だ。

子コンポーネントを使って値を更新するのか

では子コンポーネントで入力された値を親コンポーネントから渡されたpropsに反映したい場合どうすれば良いのか?結論からいうと親コンポーネントに値が変化したというイベントを伝え、親コンポーネントで値を更新するが正解だ。Vue.jsの特性上、propsは親コンポーネントの値が更新された場合、リアクティブに子コンポーネントにもその更新された値が伝達される仕組みになっている。

emitの使い方を具体例で解説

子コンポーネントで発生した更新イベントを親コンポーネントに伝達するには、Vue.jsが用意している$emitと呼ばれるメソッドを使えばよい。実際に先ほどのコードを$emitを用いてエラーが発生しないように実装してみる。

    // parent.vue
    
    <template>
      <div>
        <child
          :value="value"
          @update:value="value=$event" />
      </div>
    </template>
    
    <script>
    import Child from './components/child'
    export default {
      components: {
        Child,
      },
      data(){
        return {
          value: 'hoge',
        }
      }
    }
    </script>
    // ./components/child.vue
    
    <template>
      <div>
        <input v-model="innerVal">
      </div>
    </template>
    
    <script>
    export default {
      props: {
        value: {
          type: String,
          required: true,
          default: ''
        }
      },
    
      computed: {
        innerVal: {
          get(){
            return this.value
          },
          set(value){
            this.$emit('update:value', value)
          }
        }
      }
    }
    </script>

このように書くと子要素のinput要素に値を入力しても先ほどのエラー文は発生しない。(黄色い警告文は別のもの)

$emitに入力された値を親コンポーネントに伝達し、親コンポーネントのvalue=$eventで子コンポーネントから受け取った値で親コンポーネントのdataプロパティ更新している。これによって親コンポーネントで値が更新され、その更新後のデータが子コンポーネントに伝達される。

Vue.jsのsync構文とは

propsとemitをシンプルに記述する為のシンタックスシュガー

syncを使うと子コンポーネントに対して渡すpropsと子要素から受け取るイベントハンドラーをまとめて短いコードで綺麗に記述することができる。次の2つのプログラムの意味は全く同じだ。

    <child
      :value="value"
      @update:value="value=$event" />

    <child
      :value.sync="value" />

コードを綺麗に書くために積極的にsync構文を使った方が良い。

v-modelとsyncの使い分け

バインドするデータの個数に依存する

この記事ではv-modelが何なのかの解説はしない。興味のある方は別の記事にまとめてあるのでそちらを見て欲しい。

https://katuo-ai.com/vue-v-model/

v-modelは1つのコンポーネントに対して1つのデータを双方向バインディングするためのシンタックスシュガーだ。なので複数のデータを双方向バインディングすることはできない。一方syncは1つのコンポーネントに対して複数のデータを双方向バインディングするためのシンタックスシュガーだ。なので複数のデータを1つのコンポーネントに対して双方向バインディングしたい場合はsync構文を使えば良い。

まとめ

  • 親コンポーネントから受け取った値を子コンポーネントで直接更新してはいけない
  • emitを駆使して子コンポーネントから親コンポーネントに向けて変更イベントを追知し、親コンポーネントで値を変更する
  • 複数のデータを双方向バインディングしたいときはsync構文を使う
profile

KATUO

web developer

六本木のミドルベンチャーでwebエンジニアをやってます。普段はフロントからサーバーサイド、インフラ周りといった範囲を幅広く触ってます。

twitter-icongithub-icon

KATUBLO

Copyright since 2018 katuo All Rights Reserved.