Firebase的child_added只能获取添加的子节点。

60

从Firebase API获取:

Child Added: 此事件将为此位置的每个初始子级触发一次,并且每次添加新子级时都会再次触发。

一些代码:

listRef.on('child_added', function(childSnapshot, prevChildName) {
    // do something with the child
});

但由于该函数为此位置的每个子节点调用一次,是否有办法只获取实际添加的子节点?


32
为什么这个方法没有像它声称的那样做?它应该只做一件事——每次添加一个子元素时触发——不多不少。 - Jose Browne
4
同意@jose Browne的观点。这很令人困惑。 - Pankaj Cheema
6个回答

46

如果要跟踪自某个检查点以来添加的内容而不获取以前的记录,可以使用endAt()limit()来抓取最后一条记录:

// retrieve the last record from `ref`
ref.endAt().limitToLast(1).on('child_added', function(snapshot) {

   // all records after the last continue to invoke this function
   console.log(snapshot.name(), snapshot.val());

});

1
实际上,我发现了一种更简单的方法,因为它在达到limit()限制后仍然会通知您添加的事件。我已相应地更新了我的答案。 - Kato
2
不太清楚你的意思。这仅适用于有序集,但如果您需要对其他内容进行排序(除了有序集之外?)... 此外,您的评论并没有帮助任何人理解如何在Firebase中获取添加子项事件。 - Kato
1
回调函数仅对由 ref.endAt().limit(1) 返回的记录触发一次。它不会对添加到该记录之后的其他新记录触发。我正在使用 Angularfire 0.8.0 进行测试。 - Waseem
3
Kato在GitHub上有一个非常好的解释,在这里 - Lindauson
1
这样做仍然会在添加新子元素之前为最后一个子元素调用回调函数,对吗? - Rodrigo Ruiz
显示剩余3条评论

38

limit()方法已被弃用。 limitToLast()limitToFirst()方法取而代之。

// retrieve the last record from `ref`
ref.limitToLast(1).on('child_added', function(snapshot) {

   // all records after the last continue to invoke this function
   console.log(snapshot.name(), snapshot.val());
   // get the last inserted key
   console.log(snapshot.key());

});

6
我尝试过这种方法,但需要注意的一点是:如果最后一条记录被删除,例如用户发表了一条评论并立即将其删除(例如因为是错误的),上述 .on() 函数将再次被调用。我解决了这个问题,添加了一个时间戳,并检查新添加的子项是否小于一秒钟,如果它更旧,则是旧记录并未添加。有关详细信息,请参阅 ref.child('.info')。 - DivZero
4
明白了,这段话的意思是:我不明白,这样做难道不是只能获取最后一个项目吗?那么针对“仅获取绑定到此事件后添加的项目”呢? - Dominic
是的,我也在想同样的问题。它只获取最后一项一次,然后不会为其他添加的事件触发。 - Orlando
@DivZero 我尝试按照你说的去做,但由于我的聊天是在Android和iOS之间共享的,所以它没用,因为来自其他设备的消息有更长的延迟。这也容易出错,所以我检查了新消息是否已经在聊天中,如果是则不添加。 - Pietro Coelho
child_added 函数接受两个参数 snapshotprevChildKey,使用该方法时 prevChildKey 始终为 null。我使用 ref.endAt().on('child_added', function(snapshot, prev) {}); - av4625

7

由于调用 ref.push() 方法时没有提供数据,因此会生成基于时间的路径键,因此我这样做:

// Get your base reference
const messagesRef = firebase.database().ref().child("messages");

// Get a firebase generated key, based on current time
const startKey = messagesRef.push().key;

// 'startAt' this key, equivalent to 'start from the present second'
messagesRef.orderByKey().startAt(startKey)
.on("child_added", 
    (snapshot)=>{ /*Do something with future children*/}
);

请注意,实际上没有将任何内容写入ref.push()返回的引用(或“键”),因此无需捕获空数据。

4

我试过其他答案,但至少会针对最后一个子元素调用一次。如果您的数据中有时间键,您可以采用以下方法。

ref.orderByChild('createdAt').startAt(Date.now()).on('child_added', ...

[2018-04-27T23:58:32.727Z] @firebase/database: FIREBASE警告:使用未指定的索引。您的数据将在客户端下载和过滤。请考虑在/sms-log的安全规则中添加“.indexOn”:“createdAt”,以获得更好的性能。 - supergentle

1

Swift3 解决方案:

您可以通过下面的代码检索先前的数据:

queryRef?.observeSingleEvent(of: .value, with: { (snapshot) in
    //Your code
})

然后通过以下代码观察新数据。
queryRef?.queryLimited(toLast: 1).observe(.childAdded, with: { (snapshot) in
    //Your Code
})

你能详细说明一下吗,这样就不会让.childAdded添加所有的消息。所以,如果我正在使用一个数组,在observeSingleEvent中我想查询最后100个,然后在那之后监听所有新事件的.childAdded和.childRemoved。 - Luke Irvin
如果我正确理解您的问题,您想要在开头检索最后100条记录,然后希望监听更新。根据谷歌文档,在这种情况下您可以使用"queryLimitedToLast"观察器来实现。https://firebase.google.com/docs/database/ios/lists-of-data - Milligator

0
我发现tibeoh的回答很有效,只有一个小问题(细节太啰嗦了,不适合在评论中讨论)。
对于那些说他的回答只会触发第一个元素,并且没有后续插入的人: 在我的测试中,我发现如果您不插入比最近插入的记录更晚的记录,这是正确的。因此,这个钩子显然严格按照键的排序顺序工作,而不是基于初始化钩子之后发生的插入事件。
Eg:
last sorted key at time of binding: "b"
insert at "a" -> no trigger (comes before "b")
insert at "bbb" -> triggered
insert at "bb" -> no trigger (comes before "bbb")
insert at "bbbb" -> triggered

解决方法:使用push()函数来生成基于服务器时间的自动键。这将确保所有键都按照排序顺序插入到之前的任何内容之后,并且始终按照预期触发。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接