Vue.jsの「Avoid mutating a prop directly since ...」の警告文がなぜ発生するのかを解説する

2020-02-25

はじめに

この記事ではVue.jsで開発をしている最中「[Vue warn]: Avoid mutating a prop directly since …(以下略)」という警告文に遭遇して「どうやったらこの警告文を修正できるのか?」「なぜこの警告文が発生するのか?」などに悩んでいる人に向けて記事を書いていこうと思っております。

「Avoid mutating a prop directly since … 」の警告文が表示される状況

今回の記事で扱う「[Vue warn]: Avoid mutating a prop directly since …(以下略)」の警告文は親コンポーネントからpropsで受け取ったプロパティを子コンポーネントで更新したときに発生します。(警告文はdev toolの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側で設けているようです。

「Avoid mutating a prop directly since … 」の警告文を解決する方法

親コンポーネントで値を更新する

今回の警告文が表示される原因は親コンポーネントから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>

親コンポーネントのdataプロパティで定義したvalueを子コンポーネントにpropsで渡しています。子コンポーネントではこのvalueをinputディレクティブのv-modelに渡しています。v-modelはinput要素などと双方向データバインディングをするためのVue.jsの構文です。双方向データバインディングというのは簡単にいうと入力を受け取ったらその値をinput要素の欄に反映して、input要素の入力欄がユーザーのイベントなどによって変更されたらその変更を入力の値に反映させるといった感じです。

You can use the v-model directive to create two-way data bindings on form input, textarea, and select elements. It automatically picks the correct way to update the element based on the input type. Although a bit magical, v-model is essentially syntax sugar for updating data on user input events, plus special care for some edge cases.

引用:「Form Input Bindings

なので今回のサンプルコードですと親から受け取ったpropsのvalueをinputのv-modelに渡しているため、input要素がユーザーイベントによって書き換えられるとその書き換えられた値でpropsのvalueを更新するので想定する以下の警告文が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. Prop being mutated: "value" found in ---> at src/components/child.vue at src/App.vue

では次にこの警告文を回避するために、親コンポーネントで値を更新する方法について解説します。概要として子コンポーネントで値が変更されたときにそのイベントを親コンポーネントに伝搬するということをします。やり方はいくつかあるのですがまずはemitを使う方法を紹介します。JS側で次のようにイベント名を指定して、emitを実行すると

this.$emit('my-event')

以下のように親要素のディレクティブでイベントをキャッチしてメソッドなどを発火させることができます。

<my-component v-on:my-event="doSomething"></my-component>

イベント名はケバブケースを使うのが良いと公式ドキュメントには記載されていましたのでこれに従うことをお勧めします。では先程紹介したサンプルコードを警告文が表示されないように修正していきましょう。

// 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>

getterとsetterを定義して書いてみました。(実際はinput要素などに@updateといったイベントハンドラーを設置して、そこに直接emitを書く方が綺麗です。)やっていることはinputの要素が更新されたタイミングでsetが発火するのでこのタイミングでemitを使って変更後の値を親コンポーネントに渡して、親コンポーネント側でvalueを更新しています。こうすることで警告文が表示されることなくvalueを更新できるようになりました。

sync修飾子を使って綺麗に記述する

せっかくなのでコードを綺麗に書くためのshorthandであるsync修飾子を紹介しておきます。sync修飾子は propsとemitをシンプルに記述する為に使われます。

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

両者は同じなのでできるだけ簡潔に書くためにsync修飾子を使うことをお勧めします。先程の親コンポーネントのサンプルコードをsync修飾子を使って書き換えると以下のようになります。

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

おわりに

Vue.jsを学び始めた人はほぼ100%この警告文に遭遇すると思うので、特にフロントエンドの経験が少ない人は面倒くさいと感じるかもしれまんせんがこの制約に慣れていってください。(最終的には状態管理が楽になるはずです。)

参考文献:

KATUO
Software Engineer