始めに
Vue.jsはとても使いやすく便利ですが、その便利さゆえに仕様をしっかり理解せず、思わぬところでつまづいたりします。色んな人の見て結構こういうところでハマっているなというのがあったので、それについて紹介したいと思います。
※ここで話をするのはVue.js 2系の話で、3系から改善されているものもあります
Vue.jsの罠
配列データを更新してもリアクティブにならない
例えば入力項目を配列にして、各項目をv-modelで設定したときに、入力内容が反映されないことに気づくと思います。
<template lang="pug">
div
div
template(v-for="item in $data.arr")
input(v-model="item")
//- 配列の中身をみるが、なぜか更新されない
div {{ $data.arr }}
</template>
<script>
export default {
data() {
return {
arr: ['a', 'b', 'c'];
};
},
};
</script>
これはVue.jsが気軽にリアクティブにさせようとした弊害です。基本的には代入するだけで自動で更新してくれますが、ある条件下では更新されない罠が存在します。
- 配列の値を変えるとき
- オブジェクトのプロパティを追加・削除するとき(変更はリアクティブ)
これを解消するにはsetメソッドを使います。
<template lang="pug">
div
div
template(v-for="(item, index) in $data.arr")
input(
:value="item"
@input="(event) => { onInput(event, index); }"
)
//- 更新される
div {{ $data.arr }}
</template>
<script>
export default {
data() {
return {
arr: ['a', 'b', 'c'];
};
},
methods: {
onInput(event, index) {
// setメソッドを通して更新する
this.$set(this.$data.arr, index, event.currentTarget.value);
}
}
};
</script>
ちなみにこの問題はVue.js 3では改善されます。ただしリアクティブの実装方法をProxyに変えたことでIE非対応になりますのでご注意を。。
参照コピーをしてしまってstoreのデータをいじっていることに気づかない
Vuexのデータは直接変更せず、mutation経由で更新する必要があります。そのことを知っているにも関わらず以下のようなエラーが出て悩んでいる人を見ました。
こういう人は結構以下のようなコードを書いてdataの変数をいじっているつもりでも実はstoreの値も書き換えてしまっていました。
export default {
data() {
return {
user: null,
};
},
methods: {
async created() {
// { id: 0, name: 'hoge' }を返すAPIを想定
const user = await API.fetchUser();
// storeに保存し、かつdataにも保存する(参照コピー!)
this.$store.commit('setUser', user);
this.$data.user = user;
},
onChange(name) {
// ここでdataだけを変更しているつもりが、実はstoreの値も変更している
this.$data.user.name = name;
}
}
}
そもそも両方に保存するケースってないような気はしますが、もしstoreとdataそれぞれでいじる場合はきちんとディープコピーしてからにしましょう。
this.$store.commit('setUser', cloneDeep(user));
this.$data.user = cloneDeep(user);
scoped CSSは意外とバッティングする
Vue.jsはscoped CSSという機能があって同じクラス名であってもコンポーネントが違っていればスタイルがバッティングしないようになっています。ただし特定の条件下ではその機能が通用しない時があって、実はバッティングしている時があります。。
- 子コンポーネントの一番上のDOMのクラス
- slotによって配信されるクラス
詳細は長くなりますので以下の記事をご参照ください。
Vue.jsのscoped CSSは意外とバッティングする
v-ifでkey未設定によってDOMが更新されない時がある
v-forにkeyを設定した方が良いことは結構の人が知っていると思います。これはReactでも同じ仕様だからです。
ですがVue.jsならではの仕様で、v-ifでも再利用されてしまう場合があります。
<template lang="pug">
div
template(v-if="$data.flag")
label Username
input
template(v-else)
label Email
//- 条件が切り替わっても入力情報が残ったまま
input
</template>
この現象を確認するために改めてv-modelも含めて試しましたが、その時はきちんとdataが持っている値に更新していました。なので基本的には問題ないと思いますが、もしv-ifでDOMが更新されずに困る場合はkeyを設定しましょう。
eventの送信が受け取れない(typoに気づけない)
これはtypoによって動作しないという単純な話です。ただし、警告の表示がないため気づくのに結構時間がかかります。
<template lang="pug">
//- clickをcilckとtypoしてイベントを受け取れないが、警告されないため気付きづらい
Component(
@cilck="onClick"
)
</template>
これはどうにかしないといけないと思ってプラグインを作っていますので、もしイベント送信周りでお困りの方は是非使ってみてください。
Vueコンポーネントのイベント設定をするプラグインを作る
ただしこちらもVue.js 3になってからはきちんと送信情報を定義して、名前が間違っている場合は警告も出してくれるようになりました!
input情報とdataがあっていない
Reactだとdataの方を優先してキー入力があってもdataが更新されない場合は入力内容が反映されません。この仕様が嫌という人もいそうですが、この仕様のお陰で入力した内容がきちんとdataに入っているんだなと分かります。
しかしVue.jsはそうではなくて、input要素で入力された情報をdataに入れているため、dataに入っていなくてもinput要素には入力された状態になっています。最初に挙げた「配列データを更新してもリアクティブにならない」が良い例です。
これはいい解決法が見つかっていないので、input要素に書かれている内容がdataに入っていない可能性があるということを頭に入れておく必要があります(誰かいい方法があれば教えて欲しいです)。
終わりに
以上が思わぬところでつまづきそうなところでした。自分も含め、僕の周りで上記のようなことにハマってかなり時間を取られたことがあるので、この情報が誰かのお役に立てれば幸いです。