处理数据列表 (Web)

获取数据库引用

如需从数据库读取数据或者将数据写入数据库,您需要一个 firebase.database.Reference 实例:

Web 模块化 API

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web 命名空间型 API

var database = firebase.database();

读取和写入列表

向数据列表附加数据

使用 push() 方法可将数据附加到多用户应用的列表中。每次将新的子项添加到指定的 Firebase 引用时,push() 方法均会生成一个唯一键。为列表中的每个新元素使用这些自动生成的键,可以让多个客户端同时向同一位置添加子项,而不会引起写入冲突。push() 生成的唯一键是以时间戳为基础的,因此列表项会自动按时间顺序排列。

您可以使用对 push() 方法返回的新数据的引用,获取子项自动生成的键值或为子项设置数据。push() 引用中的 .key 属性包含自动生成的键。

然后,您可以使用这些自动生成的键简化数据结构的展平过程。如需了解详情,请参阅数据扇出示例

例如,push() 可以用于向社交应用中的博文列表添加新的博文:

Web 模块化 API

import { getDatabase, ref, push, set } from "firebase/database";

// Create a new post reference with an auto-generated id
const db = getDatabase();
const postListRef = ref(db, 'posts');
const newPostRef = push(postListRef);
set(newPostRef, {
    // ...
});

Web 命名空间型 API

// Create a new post reference with an auto-generated id
var postListRef = firebase.database().ref('posts');
var newPostRef = postListRef.push();
newPostRef.set({
    // ...
});

监听子项事件

当因为某项操作(例如通过 push() 方法添加新的子项,或通过 update() 方法更新子项)而使得某个节点的子项发生特定操作时,就会触发子项事件。

事件 典型用法
child_added 检索项列表,或监听项列表中是否添加了新项。该事件将针对每个现有的子项触发一次,并在每次向指定的路径添加新的子项时再次触发。系统将向监听器传递包含新子项数据的快照。
child_changed 监听对列表中的项做出的更改。每次修改子节点时,均会触发此事件。这包括对子节点的后代所做的任何修改。传递给事件监听器的快照中将包含子项更新后的数据。
child_removed 监听列表中是否有项被移除。移除直接子项将会触发此事件。传递给回调块的快照包含已移除的子项的数据。
child_moved 监听有序列表的项顺序是否有更改。每当有 child_changed 事件导致项目的顺序发生更改(基于您当前的排序依据方法)时,就会触发 child_moved 事件。

同时使用所有这些事件有助于监听数据库中某个特定节点是否有更改。例如,社交博客应用可以结合使用这些方法来监控博文的评论动态,如下所示:

Web 模块化 API

import { getDatabase, ref, onChildAdded, onChildChanged, onChildRemoved } from "firebase/database";

const db = getDatabase();
const commentsRef = ref(db, 'post-comments/' + postId);
onChildAdded(commentsRef, (data) => {
  addCommentElement(postElement, data.key, data.val().text, data.val().author);
});

onChildChanged(commentsRef, (data) => {
  setCommentValues(postElement, data.key, data.val().text, data.val().author);
});

onChildRemoved(commentsRef, (data) => {
  deleteComment(postElement, data.key);
});

Web 命名空间型 API

var commentsRef = firebase.database().ref('post-comments/' + postId);
commentsRef.on('child_added', (data) => {
  addCommentElement(postElement, data.key, data.val().text, data.val().author);
});

commentsRef.on('child_changed', (data) => {
  setCommentValues(postElement, data.key, data.val().text, data.val().author);
});

commentsRef.on('child_removed', (data) => {
  deleteComment(postElement, data.key);
});

监听值事件

虽然读取数据列表的推荐做法是监听子项事件,但在有些情况下监听列表引用上的值事件也很有用。

如果将一个 value 观测器 (observer) 附加到某个数据列表,系统会以单个快照的形式返回整个数据列表,然后您可以遍历该快照来访问各个子项。

即使查询仅存在一个匹配项,该快照仍是一个列表,只是仅包含一个项。如需访问该项,您需要遍历查询结果:

Web 模块化 API

import { getDatabase, ref, onValue } from "firebase/database";

const db = getDatabase();
const dbRef = ref(db, '/a/b/c');

onValue(dbRef, (snapshot) => {
  snapshot.forEach((childSnapshot) => {
    const childKey = childSnapshot.key;
    const childData = childSnapshot.val();
    // ...
  });
}, {
  onlyOnce: true
});

Web 命名空间型 API

ref.once('value', (snapshot) => {
  snapshot.forEach((childSnapshot) => {
    var childKey = childSnapshot.key;
    var childData = childSnapshot.val();
    // ...
  });
});

当您希望通过一次操作获取某个列表的所有子项(而不是监听额外子项添加事件)时,此模式可能会很有用。

排序和过滤数据

您可以使用 Realtime Database Query 类来检索按键、按值或按子项的值排序的数据。您还可以对排序后的结果进行过滤,从而得到特定数量的结果或一系列键或值。

将数据排序

如需检索经过排序的数据,请先指定一种排序依据方法,确定如何对结果排序:

方法 用法
orderByChild() 按指定子键的值或嵌套子路径对结果排序。
orderByKey() 按子键对结果排序。
orderByValue() 按子值对结果排序。

您每次只能使用一种排序依据方法。在同一查询中多次调用一种排序依据方法会引发错误。

以下示例演示了如何检索按所得星数排序的用户热门博文列表:

Web 模块化 API

import { getDatabase, ref, query, orderByChild } from "firebase/database";
import { getAuth } from "firebase/auth";

const db = getDatabase();
const auth = getAuth();

const myUserId = auth.currentUser.uid;
const topUserPostsRef = query(ref(db, 'user-posts/' + myUserId), orderByChild('starCount'));

Web 命名空间型 API

var myUserId = firebase.auth().currentUser.uid;
var topUserPostsRef = firebase.database().ref('user-posts/' + myUserId).orderByChild('starCount');

此示例定义了一个查询,如果将该查询与一个子项监听器结合使用,就能根据用户 ID 使客户端数据与数据库中用户在该路径下的博文同步,并按每篇博文获得的星数进行排序。这种使用 ID 作为索引键的方法称为“数据扇出”;如需了解详情,请参阅设计数据库的结构

调用 orderByChild() 方法可指定结果排序所依据的子键。在本例中,博文按各自 "starCount" 子项的值进行排序。当您具有与下例类似的数据时,查询也可以按嵌套的子项进行排序:

"posts": {
  "ts-functions": {
    "metrics": {
      "views" : 1200000,
      "likes" : 251000,
      "shares": 1200,
    },
    "title" : "Why you should use TypeScript for writing Cloud Functions",
    "author": "Doug",
  },
  "android-arch-3": {
    "metrics": {
      "views" : 900000,
      "likes" : 117000,
      "shares": 144,
    },
    "title" : "Using Android Architecture Components with Firebase Realtime Database (Part 3)",
    "author": "Doug",
  }
},

在本例中,通过在 orderByChild() 调用中指定嵌套子项的相对路径,我们可以按嵌套在 metrics 键下的值对列表元素进行排序。

Web 模块化 API

import { getDatabase, ref, query, orderByChild } from "firebase/database";

const db = getDatabase();
const mostViewedPosts = query(ref(db, 'posts'), orderByChild('metrics/views'));

Web 命名空间型 API

var mostViewedPosts = firebase.database().ref('posts').orderByChild('metrics/views');

如需详细了解如何对其他数据类型进行排序,请参阅如何对查询数据进行排序

过滤数据

如需过滤数据,您可以在构建查询时将某种限制方法或范围方法与排序依据方法结合使用。

方法 用法
limitToFirst() 设置要返回的项数上限:从经过排序的结果列表开头算起。
limitToLast() 设置要返回的项数上限:从经过排序的结果列表结尾算起。
startAt() 返回大于或等于指定键或值的项,具体取决于所选的排序依据方法。
startAfter() 返回大于指定键或值的项,具体取决于所选的排序依据方法。
endAt() 返回小于或等于指定键或值的项,具体取决于所选的排序依据方法。
endBefore() 返回小于指定键或值的项,具体取决于所选的排序依据方法。
equalTo() 返回等于指定键或值的项,具体取决于所选的排序依据方法。

与排序依据方法不同,您可以结合使用多种限制或范围函数。例如,您可以结合使用 startAt()endAt() 方法来将查询结果限制在指定的取值范围内。

限制结果数

您可以使用 limitToFirst()limitToLast() 方法来设置对于给定事件要同步的子项数上限。例如,如果使用 limitToFirst() 将数量上限设为 100,则最初只能接收最多 100 个 child_added 事件。如果您在 Firebase 数据库中存储的项不到 100 个,则每个项均会触发一次 child_added 事件。

随着项发生更改,对于落入查询范围的项,您将收到 child_added 事件;对于不再属于查询范围的项,您将收到 child_removed 事件,因此总数始终保持为 100。

以下示例演示了示例博客应用如何定义查询来检索所有用户中 100 篇最新博文的列表:

Web 模块化 API

import { getDatabase, ref, query, limitToLast } from "firebase/database";

const db = getDatabase();
const recentPostsRef = query(ref(db, 'posts'), limitToLast(100));

Web 命名空间型 API

var recentPostsRef = firebase.database().ref('posts').limitToLast(100);

此示例只定义了一个查询,要实际同步数据,还需要附加一个监听器

按键或值过滤

您可以使用 startAt()startAfter()endAt()endBefore()equalTo() 为查询选择任意起点、终点和等值点。这对于将数据分页或查找其子项具有特定值的项非常有用。

如何对查询数据进行排序

本部分将介绍如何通过 Query 类中的每种排序依据方法对数据进行排序。

orderByChild

使用 orderByChild() 时,包含指定子键的数据将按以下方式排序:

  1. 指定子项键的值为 null 的子项排在最前面。
  2. 接下来是指定子项键的值为 false 的子项。如果多个子项的值均为 false,则按照键以字典顺序对它们进行排序。
  3. 接下来是指定子项键的值为 true 的子项。如果多个子项的值均为 true,则按照键以字典顺序对它们进行排序。
  4. 接下来是值为数字的子项,按升序排序。如果指定子节点的多个子项具有相同的数字值,则按照键对它们进行排序。
  5. 值为字符串的子项排列在值为数字的子项后面,并按字典顺序以升序排列。如果指定子节点的多个子项具有相同的值,则按照键以字典顺序对它们进行排序。
  6. 值为对象的子项放在最后,并按照键以字典顺序排序(升序)。

orderByKey

使用 orderByKey() 对数据进行排序时,系统会按照键以升序返回数据。

  1. 键可以解析为 32 位整数的子项排在最前,按升序排列。
  2. 以字符串值作为键的子项紧随其后,按字典顺序以升序排列。

orderByValue

使用 orderByValue() 时,系统将按子项本身的值对其进行排序。排序标准与 orderByChild() 中相同,但这里使用的是节点本身的值而非指定子项键的值。

分离监听器

通过对 Firebase 数据库引用调用 off() 方法可以移除回调函数。

您可以将单个监听器作为参数传递给 off() 以将其移除。如果不传递任何参数,对数据库的某个位置调用 off() 将移除该位置上的所有监听器。

对父节点监听器调用 off() 时不会自动移除在其子节点上注册的监听器;您还必须对所有子监听器调用 off() 才能移除回调函数。

后续步骤