Vue.jsでランキング機能(Firestroe,Vuex)

2021.01.18

見出し画像

今日は以前作ったお気に入り機能の続きだと考えてください。

まだ前回の見てない〜という方は下のリンクからどーぞ。

https://note.com/tenlife/n/n8956cae544ec

記事にされたお気に入りの数に応じて記事のランキング機能を作成するというものです。

では早速。

目次

  1. まずはコードを
  2. 実装の大まかな流れ
  3. actionsの中のRankingの処理について(index.js)
  4. 後はviewで表示するだけ。
  5. 実装してみて
  6. 参考にしたサイト

まずはコードを

Vueファイルです

<template>
 <div>
   <h3>ランキング一覧<span>(お気に入りされている数が多い記事)</span></h3>
   <div v-for="post in rankings" :key="post.id">
     <p>{{post.post.title}}</p>
     <p>{{post.rank}}</p>
   </div>
 </div>
</template>

<script>
export default {
 created() {
   this.$store.dispatch('Ranking')
 },
 computed: {
   rankings() {
     // お気に入りされているpostたちを取得
     let list = this.$store.state.rankings
     function groupBy(ObjectArray) {
       let result = []
       ObjectArray.forEach(element => {
         // resultの中に既に存在しているのかを確認している
         let index = result.findIndex((res) => res.id === element.id)
         if(index <= 0) {
           result.push({
             id: element.id,
             post: element,
             rank: 1
           })
         } else {
           result[index].rank += 1
         }
       });
       return result
     }
     let resultRanking = groupBy(list)
     
     return resultRanking.sort((a, b) => {
       return (a.rank < b.rank) ? 1 : (a.rank > b.rank) ? -1 : 0
     })
   }
 }
}
</script>
index.jsです

state: {
   rankings: []
 },

mutations: {
 ranks(state, ranking_posts) {
   state.rankings = ranking_posts
 }
}

actions: {
 Ranking({commit}) {
     db.collection('favs').orderBy('post_id').onSnapshot(elements => {
       let posts = []
       elements.forEach(ele => {
         let fav = ele.data()
         fav.id = ele.id
         db.collection('posts').doc(fav.post_id).onSnapshot(res => {
           let post = res.data()
           post.id = res.id
           posts.push(post)
         })
       })
       commit('ranks', posts)
     })
   }
}

実装の大まかな流れ

ライフサイクルメソッドを使用して、index.jsファイルのRankingを動かすようにしている。(actionsの中にあるのでdispatchで呼び出します)

actionsのRankingではorderByメソッドを使用したりして値を取得します。最終的には取得した値をcommitでmutationsのrankに渡します。

commitでは取得した値をstateのrankingsに代入しています。

以上がうまく処理された場合はVueファイルの方に戻り、computedでstateにあるrankingsを取得します。(正しく処理されている場合は値が取得できます)

ここからが大事です。取得した値(postの配列)に対してforeachを使い同じpostが来た場合と新しいpostが来た場合で処理を分けています。

具体的には後述します。

その処理を通った後はとv-forを使いview画面に表示させるだけです。ここもポイントがあるので説明します。

actionsの中のRankingの処理について(index.js)

この処理はVueファイルのライフサイクルメソッドの中にあるthis.$store.dispatch(‘Ranking’)この記述によって呼び出されます。

  Ranking({commit}) {
     db.collection('favs').orderBy('post_id').onSnapshot(elements => {
       let posts = []
       elements.forEach(ele => {
         let fav = ele.data()
         fav.id = ele.id
         db.collection('posts').doc(fav.post_id).onSnapshot(res => {
           let post = res.data()
           post.id = res.id
           posts.push(post)
         })
       })
       commit('ranks', posts)
     })
   }

db.collectionでfavsテーブルからpost_idを基準にして取得します。orderByを使っているのでpost_id順になります。

上で取得したelementsをforeachで回しfav1つを取り出します。取り出したfav.post_idを使ってお気に入りされているpostを取得します。

onSnapshotを使用することによって、リアルタイムリスナーになり、データベースの変更を検知して動くようになっています。

let posts = []に値が格納されているので、commitで格納されている値をmutatiosのranksに飛ばします。

ranksでは用意されてあるstateのrankingsに代入しているだけです。

ComputedにあるRankingsについて

 rankings() {
     // お気に入りされているpostたちを取得
     let list = this.$store.state.rankings
     function groupBy(ObjectArray) {
       let result = []
       ObjectArray.forEach(element => {
         // resultの中に既に存在しているのかを確認している
         let index = result.findIndex((res) => res.id === element.id)
         if(index <= 0) {
           result.push({
             id: element.id,
             post: element,
             rank: 1
           })
         } else {
           result[index].rank += 1
         }
       });
       return result
     }
     let resultRanking = groupBy(list)
     
     return resultRanking.sort((a, b) => {
       return (a.rank < b.rank) ? 1 : (a.rank > b.rank) ? -1 : 0
     })
   }

先ほどの処理で保存した、state.rankingsを取得します(listに代入)。groupBy(list)としてgroupByを実行します。

受け取った後はforeachで回し、もうすでにランキングリストに載っているpostなのかを判断しています。

ランキングリストであるresultを使用してforeachで回されたelementとidを使って比較しています。

ランキングリストに載っていないpostであればresult.pushで追加します。

もうすでにあった場合はrankにプラス1をつけ、お気に入り数としてカウントします。

上の処理が終わると値がresultの中に代入されます。中身は以下のようになっています。

0: {id: "ocey1bJHLOS7kmM3IxKH", post: {…}, rank: 2}
1: {id: "JHLOS7kmM3IxKHcy1bJH", post: {…}, rank: 1}

postの中身です
post:
    title: "ほほ"
    user: Object
    text: "いいね"
    public: true
    id: (...)

このpostの中に実際の記事の値が入っている形になります。オブジェクトとして保存されているということです。

上記の処理が全て終わるとgroupByの返り値になります。resultRankingです。

resultRankingこれをsortを使ってrankの数が多い順に並び替えています。この処理については参考にしたサイトに貼ってあります。

後はviewで表示するだけ。

v-forを使ってrankingsから値を取得し、表示させるだけですね!

ここで気おつけて欲しいのが1つ。先ほども確認したのですが、postはオブジェクトになっているということです。v-forで回して使う場合には1つ階層が深くなっているようなイメージかと思います。

   <div v-for="post in rankings" :key="post.id">
     <p>{{post.post.title}}</p>
     <p>{{post.rank}}</p>
   </div>

実装してみて

大きくは分けて2箇所が大事な処理になっているかなと感じました。

少し複雑に感じますが、処理を分けて考えると意外といけるかなと思います。

次はフォロー機能実装しようかな〜といった感じです。

そんな感じで今回は

以上!!

参考にしたサイト

Vue.jsのv-forでの並び順を指定したキーの値順番にしたり、昇順、降順を切り替えたりするVue.jsで使用できるv-forは簡単にリストレンダリングできとても便利です。ただ、レンダリングの順番を指定したキーで並www.virment.com

vueのv-forを使って良いねの数が多い順に表示し、同じ投稿はまとめて表示させたい(ランキング機能)|teratailvue.js 勉強中です vueのv-forを使って良いねの数が多い順に表示し、同じ投稿はまとめて表示させたい(ランキンteratail.com