javascript - 监控 Firebase 中的子节点值并分配数字排名

标签 javascript reactjs firebase firebase-realtime-database

我有 upvote 和 downvote 功能,它们正在执行交易并正确操纵我的体育运动员姓名数据库中的投票计数。

投票为 1 和 -1。然后计算玩家的总票数,并将其作为 votes

放入数据库

db

每次投票时,我想要一个函数或一段代码来查看 players 中的所有名字,并为每个名字分配一个数字来描述他们在数据库中每个人中的排名(根据他们的投票从多到少)(即 James 有 10 票赞成票和 0 票反对票(votes = 10),他排名第一。John 有 10 票赞成票和 1 票反对票(votes = 9 ) 并且排名第 2。如果我给 John 投票,我应该刷新页面并看到它们并列在 1。这在一定程度上适用于我下面的当前代码,但是一旦我开始通过输入和进行一些赞成票、反对票和撤回我的选票,voteCount 变量变得很奇怪,排名偏离路线。我相信有一种更简单或更好的方法可以做到这一点。

orderedPlayersRank 是一个按votes 对玩家进行排序的数组,最好的在前,最差的在后。所以我排名第一的人应该始终在 orderedPlayersRank 数组中排在第一位。

global vars

let prevPlayerVotes = 0
let rankCount = 1

      //RANKING CODE    

      //orderedPlayersRank sorts players from highest votes to lowest

      orderedPlayersRank.map((player) => {
      this.database.child(player.id).transaction(function(player){
      if (player.votes >= prevPlayerVotes) {
          prevPlayerVotes = player.votes
          player.rank = rankCount
      } else if (player.votes < prevPlayerVotes) {
          rankCount++
          player.rank = rankCount
          prevPlayerVotes = player.votes
      } else {
          console.log("Rank calculation error.")
      }
      return player;
   })
})

这是我完整的点赞功能,仅供引用。我将上面的代码放在底部有 //ranking functionality 注释的地方。在那个位置,只要进行有效投票,就会运行排名代码。我也会将相同的代码放入 downvote 函数中。

upvotePlayer(playerId) {
const players = this.state.players;

const orderedPlayersRank = _.orderBy(players, ['votes'], ['desc'])
if (this.state.user) {
    let ref = firebase.database().ref('/players/' + playerId + '/voters');
    ref.once('value', snap => {
        var value = snap.val()
        if (value !== null) {
            ref.child(this.uid).once('value', snap => {
                if (snap.val() === 0 || snap.val() == null) {
                    ref.child(this.uid).set(1);
                    this.database.child(playerId).transaction(function(player) {
                        if (player) {
                            player.votes++
                        }
                        return player;
                    })
                } else if (snap.val() === -1) {
                    ref.child(this.uid).set(1);

                    //Added vote balancing 
                    this.database.child(playerId).transaction(function(player) {
                        if (player) {
                            player.votes++
                                player.votes++
                        }
                        return player;
                    })
                } else if (snap.val() === 1) {
                    ref.child(this.uid).set(0);

                    //Added vote balancing
                    this.database.child(playerId).transaction(function(player) {
                        if (player) {
                            player.votes--
                        }
                        return player;
                    })
                } else {
                    console.log("Error in upvoting. snap.val(): " + snap.val())
                }
            })
        } else {
            ref.child(this.uid).set(1);
            this.alertUpVote()
            //Added vote balancing
            this.database.child(playerId).transaction(function(player) {
                if (player) {
                    player.votes++
                        console.log("Player added")
                }
                return player;
            })
        }
    });
//ranking functionality here
} else {
    this.alertNotLoggedIn()
    console.log("Must be logged in to vote.")
}
}

正如我所说,赞成票功能运行良好。我只是在寻找有关我正在努力解决的排名功能的一些建议。我感谢任何帮助,并可以提供任何其他相关代码

最佳答案

因此交易可以运行multiple times before completion如果数据在交易解决之前发生变化。这可能会导致范围外的任何变量变得不同步(即 rankCountprevPlayerVotes)。另一个原因可能是您正在遍历 orderedPlayersRank 并为每次调用 transaction 返回一个 Promise。这将导致同时读取/修改 prevPlayerRankrankCount,而不是像我假设您期望的那样按顺序读取/修改。

一个解决方案可能只是在列表上使用 orderByChild('votes') 并使用索引与检查先前的值配对来确定显示时的排名或在进行更改时设置排名投票(通过 Firebase 函数或观察者)。

例。 (火力地堡功能)

export var rank = functions.database.ref('players/{playerId}/votes')
.onUpdate((change, context) => {
  // list by 'votes' in ascending order
  var orderedListRef = change.after.ref.root.child('players').orderByChild('votes')
  var oldVotes = change.before.val()
  var newVotes = change.after.val()
  var notChanged = 0
  var changeRank = 0
  // went higher in the list so bump every player passed by 1
  if (newVotes > oldVotes) {
    // Range: [oldVotes, newVotes]
    orderedListRef = orderedListRef.startAt(oldVotes).endAt(newVotes)
    changeRank = 1
    notChanged = newVotes
  } else {// went lower in the list so bump every player passed by -1
    // Range: [newVotes, oldVotes]
    orderedListRef = orderedListRef.startAt(newVotes).endAt(oldVotes)
    changeRank = -1
    notChanged = oldVotes
  }
  return orderedListRef.once('value')
  .then((ss) => {
    var promises = []
    var playersPassed = 0
    // IMPORTANT: must use `forEach` to ensure proper order
    ss.forEach((playerSS) => {
      if (playerSS.key === context.params.playerId) {
        return
      }
      playersPassed += 1
      if (playerSS.child('votes').val() === notChanged) {
        return
      }
      // use transaction to ensure proper number of bumps if multiple changes at once
      promises.push(playerSS.child('rank').ref.transaction((rank) => {
        return rank + changeRank
      }))
    })
    // use transaction to adjust rank by players passed
    promises.push(change.before.ref.parent.child('rank')
    .transaction((rank) => {
      return rank - playersPassed * changeRank
    }))
    return Promise.all(promises)
  })
})

初始化示例

export var initRank = functions.database.ref('players/{playerId}/votes')
.onCreate((snapshot, context) => {
  // list by 'votes' in ascending order
  return Promise.all([
    snapshot.ref.root
      .child('players')
      .orderByChild('votes')
      .startAt(snapshot.val())
      .once('value')
      .then((ss) => {
        return snapshot.ref.parent.child('rank').transaction((rank) => {
          if (rank) {
            return rank + ss.numChildren
          }
          return ss.numChildren
        })
      }),
    snapshot.ref.root
      .child('players')
      .orderByChild('votes')
      .endAt(snapshot.val()-1)
      .once('value')
      .then((ss) => {
        var promises = []
        ss.forEach((playerSS) => {
          promises.push(playerSS.child('rank').ref.transaction((rank) => {
            if (rank) {
              return rank + 1
            }
          })
        })
        return Promise.all(promises)
      })
  ])
})

使用这种方法,您需要将新创建的玩家的等级设置为最高等级。希望这对您有所帮助!

关于javascript - 监控 Firebase 中的子节点值并分配数字排名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49908508/

相关文章:

ios - 在 Swift 中使用 firebase 创建新用户

javascript: 在不同的iframe中加载相同的url,如何仅使用一个http请求?

javascript - 当用户想要关闭网页时显示 iframe

javascript - 在 React 和 TypeScript 中使用正确的引用模式时,Refs 是空对象 {}

javascript - ReactJS - 使用 axios 响应抛出提交错误

火警警告 : Invalid query string segment - Warning when deploying simple Firebase Cloud function

javascript - js中的正则表达式比较

javascript - 类型 '(s: string) => string' 不可分配给类型 '<T>(t: T) => T'

javascript - 如何修改 Material-UI <Select> 下拉 div 的 z-index?

android - 将 ProGuard 映射文件上传到 Firebase