syon

A fashion developer. Interested in life engineering.

Vue.jsのv-for内でバインドした内容が一致しない現象

15 May 2019 » Vue.js

やりたいこと

Vue.js の v-for を使ってリストレンダリングしたい。 Vuex の Getters でソートしたものを表示したい。 今回は、リストの要素それぞれが持つポイントでランキングを作ろうとしていた。 要素は画像URLや記事URLを持つ。

不具合

ランキング内項目の画像やタイトルリンクのURLが、 他のそれのものと入れ替わったような表示がされた。

はじめは画像のキャッシュを疑い、v-bind:key を見直したが変化なし。

開発は Nuxt.js で行っており、watch を経由して一部の要素に変更を加えると まるで「バレた!」とでも言うかのようにそそくさと正しい表示に直った。 しかし、ブラウザでリロードし直すと再度不一致を起こした。

原因の切り分けのために表示要素を削ったりして探ったところ、 望んだ結果となっていない箇所は v-bind を行っている箇所に限定されていることがわかった。 つまり <img :src="theSrc"><a :href="theUrl"> である。

原因

原因といっても、この現象を引き起こしている直接原因でしかないが、ソートまわりにあった。 ソートせずにリストレンダリングすると正常に表示されるため。 ではソート処理の何がいけなかったのか? slice() をして元の配列を改変するようなことをしていないし…。

今回作っていたのはランキングだと述べた。 よく見ると、同率順位でポイントが並んだ中でシャッフルが起きていた。 そういう場合は元の並びをキープしてくれればそれでよいと思っていても、そう動いてくれないならば仕方がない。

解決方法

修正対象は sort()0 を返した箇所。 最終的に必ず一意な並び順となるように指定する。 今回は id (文字列) を使って指定した。

export const getters = {
  sortedList(state) {
    return state.list
      .slice()
      .sort((a, b) => {
        if (a.point > b.point) return -1
        if (a.point < b.point) return 1
        if (a.id < b.id) return -1
        if (a.id > b.id) return 1
        return 0
      })
  },
  // :
}

これで無事内容が正しく表示されるようになった。めでたし。