4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Vue.jsの罠

Last updated at Posted at 2021-01-04

始めに

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経由で更新する必要があります。そのことを知っているにも関わらず以下のようなエラーが出て悩んでいる人を見ました。

スクリーンショット 2021-01-04 14.50.13.png

こういう人は結構以下のようなコードを書いてdataの変数をいじっているつもりでも実はstoreの値も書き換えてしまっていました。

参照コピーでうっかり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でも再利用されてしまう場合があります。

input要素が差し変わらない
<template lang="pug">
div
  template(v-if="$data.flag")
    label Username
    input
  template(v-else)
    label Email
    //- 条件が切り替わっても入力情報が残ったまま
    input
</template>

key による再利用可能な要素の制御

この現象を確認するために改めてv-modelも含めて試しましたが、その時はきちんとdataが持っている値に更新していました。なので基本的には問題ないと思いますが、もしv-ifでDOMが更新されずに困る場合はkeyを設定しましょう。

eventの送信が受け取れない(typoに気づけない)

これは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に入っていない可能性があるということを頭に入れておく必要があります(誰かいい方法があれば教えて欲しいです)。

終わりに

以上が思わぬところでつまづきそうなところでした。自分も含め、僕の周りで上記のようなことにハマってかなり時間を取られたことがあるので、この情報が誰かのお役に立てれば幸いです。

4
1
1

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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?