Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
161
Help us understand the problem. What is going on with this article?
@bobu_web

Vue.jsでビューの変更がされないときに疑うこと+主な解決策方法

More than 1 year has passed since last update.

これはcloudpack あら便利カレンダー 2018 の記事です。

Vue.jsでDOMが更新されない問題はわりとよくあたります。
ちょうど昨日社内で相談されたので、せっかくなので記事にまとめてみました。

ビューが更新されないあるある

配列、オブジェクトの更新が検出できない

:confused: 配列のケース

  1. インデックスでアイテムを直接設定するとき 例: vm.items[indexOfItem] = newValue
  2. 配列の長さを変更するとき 例: vm.items.length = newLength

:relaxed: 解決方法
1. $setを使う
2. Vueが監視出来る配列のメソッドを使う push(), pop(), shift(), unshift(), splice(), sort(), reverse()

参考リンク => https://jp.vuejs.org/v2/guide/reactivity.html#配列の変化を検出

:confused: オブジェクトのケース

  1. プロパティを追加・削除したとき

:relaxed: 解決方法
1. $setを使う
2. Object.assignで新しいオブジェクトとして入れ直す

参考リンク => https://jp.vuejs.org/v2/guide/reactivity.html#変更検出の注意事項

new Vue({
  el: "#app",
  data: {
    user: {
      name: "Nobu",
      icon: "https://benri2018.rnd.cloudpack.jp/images/2p.png",
      description: "Vue大好き"
    },
    fruits: [
        'apple', 'banana', 'lemon', 'orange'
    ],
    colorSchemes: [
        'red', 'blue', 'green', 'black'
    ]
  },
  methods: {
    toggle: function(todo){
        todo.done = !todo.done
    },
    addColor(color) {
        // userにcolorを追加してもビューがかわらない
        this.user.color = color;

      // $setを利用する、もしくはオブジェクトを入れ直すことで反映される
      // this.$set(this.user, "color", color);
      // this.user = Object.assign({}, this.user, { color });
    },
    changeArrayItem(index) {
        // インデックスでアイテムを直接指定してもビューが変わらない
      this.fruits[index] = "変更済み";

      // $setを利用する、Vueが監視出来る配列のメソッドを使う
      // this.$set(this.fruits, index, "$setで変更済み");
      // this.fruits.splice(index, 1, "spliceで変更済み");
    }
  }
})

サンプルコード => https://jsfiddle.net/nobu222/9nf7mcLx/

DOMが再レンダリングされず、変更前の値が残っている

:confused: Vue.jsのレンダリングについて

Vue.jsはDOM要素を可能な限り効率的に描画しようとします。
公式に挙げられている典型的な例です。
以下のような場合v-ifによりテンプレートが切り替えられますが、
<input>要素は再描画されず、input要素に入力値がそのまま残ります。

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address">
</template>

このVueでのDOMレンダリングが行われるタイミングを理解していないと
他のライブラリ(たとえばjQueryプラグイン!)を併用しているとき、
ライブラリの初期化処理がうまくいかなかったり、DOMが本来持っている初期動作がなされず
意図しない動作になってしまいます。

:relaxed: 解決方法
1. v-ifでDOMを再レンダリングさせる (v-ifのレンダリングは遅延レンダリングなので、値がtrueになって初めてレンダリングされます)
2. key属性を使ってDOMを再利用させないようにする

以下の様にkeyを利用することで入力値が毎回更新されるようになります

<!-- ユニークな値をkeyに設定することでDOMのレンダリングを制御できます-->
<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username" key="input-email">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address" key="input-email">
</template>

DOMの更新が非同期キューであることが考慮されていない

:confused: Vueでバインドされた値を変更した直後にVue外からアクセスすると変更されていない

バインドされた値の更新による、DOMの更新は非同期でなされます。内部的にはPromiseやsetTimeoutを利用し非同期に更新がキューイングされています。
以下の例はあまりピンと来ないものですが、これはDOMの状態に依存する他のライブラリなどを併用しているときなど起こりがちです。

意図しない挙動をする例

new Vue({
  el: "#app",
  data: {
    message: "not updated."
  },
  computed: {
    playVideo() {
        return this.videos[this.playVideoIndex]
    }
  },
  methods: {
    update() {
      this.message = 'updated.'
      const msg = document.getElementById('msg');
      console.log(msg.textContent); // log is 'not updated.'
    }
  }
})

:relaxed: 解決方法
1. nextTickを使う

nextTickは実行時点から次のキューイングが実行されDOMが更新されたタイミングでコールバック関数が呼び出されます。
さきほどの例だとspan要素のテキストが書き換わったタイミングで呼び出され、意図した動作で実行が出来ます。

new Vue({
  el: "#app",
  data: {
    message: "not updated."
  },
  computed: {
    playVideo() {
        return this.videos[this.playVideoIndex]
    }
  },
  methods: {
    update() {
      this.message = 'updated.'
      const msg = document.getElementById('msg');
      this.$nextTick(() => {
          console.log(msg.textContent); // log is 'updated.'
      })
    }
  }
})

サンプルコード => https://jsfiddle.net/nobu222/q1wrh7om/6/

以上、Vue.jsのデータバインディングとDOMのレンダリングで意図しない挙動をするよくある例を上げました。
今回の記事にあたって久しぶりにVue.jsのドキュメントをつらつらと見ていたのですが、
結構新しい発見がありました。(keyとかちゃんと知らんかった。。)
またそのへんはどこかでピックアップしたいと思います。それでは。

ほんとうは番外編でvue-routerで切り替えたコンポーネントは再利用されてcreatedとか発火しないですよーってのも書こうと思ってましたが、力尽きました。
なので参考リンクだけ => 動的ルートマッチング: パラメーター変更の検知

161
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
bobu_web
おもにフロントエンド系、その流れでiOS、Androidも最近やってます

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
161
Help us understand the problem. What is going on with this article?