我知道这可能看起来很奇怪,但这实际上是预期的行为。
为了保证本地事件可以在不先与服务器通信的情况下立即触发,Firebase 不保证始终按排序顺序调用 child_added 事件。
如果这令人困惑,请这样想:如果您根本没有互联网连接,并且您设置了一个 limit(50),然后在该引用上调用 push(),您是否希望立即触发一个事件. 但是,当您重新连接到服务器时,可能会发现服务器上在您推送的项目之前还有其他项目,然后将在您添加的事件之后触发它们的事件。在您的示例中,问题与本地缓存的数据有关,而不是与本地客户端写入的数据有关,但同样的原则也适用。
有关为什么事情需要以这种方式工作的更详细示例,请想象 2 个客户 A 和 B:
- 离线时,客户端 A 调用 push() 并设置一些数据
- 在线时,客户端 B 添加一个 child_added 监听器来读取消息
- 客户端 B 然后调用 push()。它推送的消息会立即在本地触发 child_added 事件。
- 客户端 A 重新联机。Firebase 同步数据,客户端 B 会为该数据触发 child_added 事件。
现在,请注意,即使客户端 A 添加的消息在列表中排在第一位(因为它具有较早的时间戳),该事件也会在第二个触发。
如您所见,您不能总是依靠事件顺序来反映 Firebase 子项的正确排序顺序。
但别担心,你仍然可以获得你想要的行为!如果您希望数据按排序顺序显示,而不是按事件到达客户端的顺序显示,您有两种选择:
1)(天真的方法)使用“值”事件而不是 child_added,并在每次使用 forEach 触发时重新绘制整个项目列表。值事件只会触发完整的数据集,因此 forEach 将始终按顺序枚举所有事件。但是这种方法有几个缺点:(1)值事件在从服务器加载初始状态之前不会触发,因此如果应用程序在“离线模式”下启动,直到可以连接网络,它才会起作用已确立的。(2) 它低效地为每次更改重新绘制所有内容。
2) (更好的方法)在 on() 的回调中使用 prevChildName 参数。除了快照之外,当项目按排序顺序放置时,on() 的回调会在查询中传递上一个子项的名称。这允许您以正确的顺序呈现子级,即使事件被无序触发。请参阅:https ://www.firebase.com/docs/javascript/firebase/on.html
请注意,prevChildName 仅给出查询中的前一个孩子,而不是整个 Firebase 位置。因此,查询开头的孩子的 prevChildName 将为 null,即使在它之前的服务器上有一个孩子。
我们的排行榜示例展示了一种操作 DOM 以确保以正确顺序呈现事物的方法。请参阅:
https ://www.firebase.com/tutorial/#example/leaderboard