了解如何将 Firebase 用于 Web

1. 概览

在此 Codelab 中,您将学习使用 Firebase 创建交互式 Web 应用的一些基础知识。您将使用多个 Firebase 产品构建一个活动回复和留言板聊天应用。

此步骤的屏幕截图

学习内容

  • 使用 Firebase Authentication 和 FirebaseUI 对用户进行身份验证。
  • 使用 Cloud Firestore 同步数据。
  • 编写 Firebase 安全规则以保护数据库。

所需条件

  • 您选择的浏览器,例如 Chrome。
  • 能够访问 stackblitz.com(无需帐号或登录)。
  • Google 帐号,如 Gmail 帐号。我们建议您使用已用于 GitHub 帐号的电子邮件帐号。这允许您使用 StackBlitz 中的高级功能。
  • 此 Codelab 的示例代码。请参阅下一步,了解如何获取代码。

2. 获取起始代码

在此 Codelab 中,您将使用 StackBlitz 构建应用,StackBlitz 是一个在线编辑器,其中集成了多个 Firebase 工作流。Stackblitz 无需安装软件,也无需特殊的 StackBlitz 帐号。

利用 StackBlitz 可以与他人分享项目。知道您的 StackBlitz 项目网址的其他用户可以看到您的代码并为您的项目创建分支,但无法修改您的 StackBlitz 项目。

  1. 转到以下网址查看起始代码:https://stackblitz.com/edit/firebase-gtk-web-start
  2. 在 StackBlitz 页面顶部,点击 Fork

此步骤的屏幕截图

现在,您有一份起始代码的副本作为自己的 StackBlitz 项目,该项目具有唯一的名称和唯一的网址。您的所有文件和更改都会保存在此 StackBlitz 项目中。

3. 修改事件信息

此 Codelab 的入门资料为 Web 应用提供了一些结构,包括该应用的一些样式表和几个 HTML 容器。在此 Codelab 的后面部分,您会将这些容器挂接到 Firebase。

首先,我们来深入了解一下 StackBlitz 界面。

  1. 在 StackBlitz 中,打开 index.html 文件。
  2. 找到 event-details-containerdescription-container,然后尝试修改一些活动详细信息。

在您编辑文本时,StackBlitz 中的自动页面重新加载会显示新的活动详细信息。好的,是吗?

<!-- ... -->

<div id="app">
  <img src="..." />

  <section id="event-details-container">
     <h1>Firebase Meetup</h1>

     <p><i class="material-icons">calendar_today</i> October 30</p>
     <p><i class="material-icons">location_city</i> San Francisco</p>

  </section>

  <hr>

  <section id="firebaseui-auth-container"></section>

  <section id="description-container">
     <h2>What we'll be doing</h2>
     <p>Join us for a day full of Firebase Workshops and Pizza!</p>
  </section>
</div>

<!-- ... -->

应用的预览应如下所示:

应用预览

此步骤的屏幕截图

4.创建和设置 Firebase 项目

显示活动信息对邀请对象来说很有用,但单纯显示活动对每个人来说并不实用。我们来为此应用添加一些动态功能。为此,您需要将 Firebase 关联到您的应用。要开始使用 Firebase,您需要创建并设置 Firebase 项目。

创建 Firebase 项目

  1. 登录 Firebase
  2. 在 Firebase 控制台中,点击添加项目(或创建项目),然后将您的 Firebase 项目命名为 Firebase-Web-Codelab

    此步骤的屏幕截图

  3. 点击各个项目创建选项。如果系统提示,请接受 Firebase 条款。在 Google Analytics(分析)屏幕上,点击“不启用”,因为您不会为此应用使用 Google Analytics(分析)。

如需详细了解 Firebase 项目,请参阅了解 Firebase 项目

在控制台中启用并设置 Firebase 产品

您正在构建的应用使用了多个适用于 Web 应用的 Firebase 产品:

  • Firebase AuthenticationFirebase 界面:可让用户轻松登录您的应用。
  • Cloud Firestore:用于将结构化数据保存在云端,并在数据发生变化时获得即时通知。
  • 使用 Firebase 安全规则来保护数据库。

其中部分产品需要进行特殊配置,或者需要使用 Firebase 控制台启用。

为 Firebase Authentication 启用电子邮件登录功能

为了允许用户登录 Web 应用,您将在此 Codelab 中使用电子邮件地址/密码登录方法:

  1. 在 Firebase 控制台的左侧面板中,点击构建 > 身份验证。然后点击开始使用。您现在位于身份验证信息中心,您可以在其中查看已注册的用户、配置登录提供方以及管理设置。

    此步骤的屏幕截图

  2. 选择登录方法标签页(或点击此处直接进入该标签页)。

    此步骤的屏幕截图

  3. 在提供商选项中点击电子邮件地址/密码,将开关切换为启用,然后点击保存

    此步骤的屏幕截图

设置 Cloud Firestore

Web 应用使用 Cloud Firestore 保存聊天消息并接收新的聊天消息。

Cloud Firestore 的设置方法如下:

  1. 在 Firebase 控制台的左侧面板中,依次点击构建 > Firestore 数据库。然后,点击创建数据库
  2. 点击创建数据库

    此步骤的屏幕截图

  3. 选择以测试模式开始选项。阅读有关安全规则的免责声明。测试模式可确保您在开发过程中可以随意向数据库写入数据。点击 Next(下一步)。

    此步骤的屏幕截图

  4. 选择数据库的位置(只需使用默认位置即可)。但请注意,此地点以后无法更改。

    此步骤的屏幕截图

  5. 点击完成

5. 添加和配置 Firebase

现在,您已经创建了 Firebase 项目并启用了一些服务,接下来您需要告知代码要使用 Firebase 以及要使用哪个 Firebase 项目。

添加 Firebase 库

为了让您的应用能够使用 Firebase,您需要向应用添加 Firebase 库。您可以通过多种方法执行此操作,如 Firebase 文档中所述。例如,您可以从 Google 的 CDN 添加库,也可以使用 npm 在本地安装这些库,如果您使用的是 Browserify,则可以将其打包在您的应用中。

StackBlitz 提供自动捆绑功能,因此您可以使用 import 语句来添加 Firebase 库。您将使用模块化 (v9) 库,这些库通过一种称为“摇树优化”的过程有助于减小网页的总体大小。如需详细了解模块化 SDK,请参阅相关文档

如需构建此应用,您需要使用 Firebase Authentication、FirebaseUI 和 Cloud Firestore 库。对于此 Codelab,index.js 文件的顶部已包含以下 import 语句,并且随着我们不断从每个 Firebase 库导入更多方法:

// Import stylesheets
import './style.css';

// Firebase App (the core Firebase SDK) is always required
import { initializeApp } from 'firebase/app';

// Add the Firebase products and methods that you want to use
import {} from 'firebase/auth';
import {} from 'firebase/firestore';

import * as firebaseui from 'firebaseui';

将 Firebase Web 应用添加到您的 Firebase 项目

  1. 返回 Firebase 控制台,点击左上角的项目概览,前往项目的概览页面。
  2. 在项目概览页面的中心位置,点击 Web 图标 Web 应用图标 即可创建一个新的 Firebase Web 应用。

    此步骤的屏幕截图

  3. 使用别名 Web App 注册应用。
  4. 对于此 Codelab,请勿选中同时为此应用设置 Firebase Hosting 旁边的复选框。您将暂时使用 StackBlitz 的预览窗格。
  5. 点击 Register app(注册应用)。

    此步骤的屏幕截图

  6. Firebase 配置对象复制到剪贴板。

    此步骤的屏幕截图

  7. 点击前往控制台。将 Firebase 配置对象添加到您的应用中:
  8. 返回 StackBlitz,打开 index.js 文件。
  9. 找到 Add Firebase project configuration object here 注释行,然后将配置代码段粘贴到注释的正下方。
  10. 添加 initializeApp 函数调用,以使用您的独特的 Firebase 项目配置来设置 Firebase。
    // ...
    // Add Firebase project configuration object here
    const firebaseConfig = {
      apiKey: "random-unique-string",
      authDomain: "your-projectId.firebaseapp.com",
      databaseURL: "https://your-projectId.firebaseio.com",
      projectId: "your-projectId",
      storageBucket: "your-projectId.appspot.com",
      messagingSenderId: "random-unique-string",
      appId: "random-unique-string",
    };
    
    // Initialize Firebase
    initializeApp(firebaseConfig);
    

6. 添加用户登录信息(回复)

现在,您已将 Firebase 添加到该应用,接下来可以设置一个回复按钮,使用 Firebase Authentication 注册用户。

使用电子邮件登录和 FirebaseUI 对用户进行身份验证

您需要提供一个回复按钮来提示用户使用其电子邮件地址登录。为此,您可以将 FirebaseUI 与回复按钮关联起来。FirebaseUI 是一个库,可为您提供基于 Firebase 身份验证的预构建界面。

FirebaseUI 需要一项配置(请参阅此文档中的选项),该配置需要执行以下两项操作:

  • 告知 FirebaseUI 您要使用电子邮件地址/密码登录方法。
  • 处理登录成功的回调,并返回 false 以避免重定向。您不希望页面刷新,因为您构建的是单页 Web 应用。

添加代码以初始化 FirebaseUI 身份验证

  1. 在 StackBlitz 中,打开 index.js 文件。
  2. 在顶部找到 firebase/auth import 语句,然后添加 getAuthEmailAuthProvider,如下所示:
    // ...
    // Add the Firebase products and methods that you want to use
    import { getAuth, EmailAuthProvider } from 'firebase/auth';
    
    import {} from 'firebase/firestore';
    
  3. initializeApp 之后立即保存对 auth 对象的引用,如下所示:
    initializeApp(firebaseConfig);
    auth = getAuth();
    
  4. 请注意,起始代码中已提供了 FirebaseUI 配置。它已设置为使用电子邮件身份验证提供方。
  5. index.js 中的 main() 函数底部,添加 FirebaseUI 初始化语句,如下所示:
    async function main() {
      // ...
    
      // Initialize the FirebaseUI widget using Firebase
      const ui = new firebaseui.auth.AuthUI(auth);
    }
    main();
    
    

在 HTML 中添加回复按钮

  1. 在 StackBlitz 中,打开 index.html 文件。
  2. event-details-container 中添加回复按钮的 HTML,如以下示例所示。

    请务必使用相同的 id 值,如下所示,因为对于此 Codelab,index.js 文件中已经提供了这些特定 ID 的钩子。

    请注意,index.html 文件中有一个 ID 为 firebaseui-auth-container 的容器。您需要将此 ID 传递给 FirebaseUI 以保留您的登录信息。
    <!-- ... -->
    
    <section id="event-details-container">
        <!-- ... -->
        <!-- ADD THE RSVP BUTTON HERE -->
        <button id="startRsvp">RSVP</button>
    </section>
    <hr>
    <section id="firebaseui-auth-container"></section>
    <!-- ... -->
    
    应用预览

    此步骤的屏幕截图

  3. 在回复按钮上设置监听器,并调用 FirebaseUI 启动函数。这会告知 FirebaseUI 您希望看到登录窗口。

    将以下代码添加到 index.js 中的 main() 函数底部:
    async function main() {
      // ...
    
      // Listen to RSVP button clicks
      startRsvpButton.addEventListener("click",
       () => {
            ui.start("#firebaseui-auth-container", uiConfig);
      });
    }
    main();
    

测试登录应用

  1. 在 StackBlitz 的预览窗口中,点击“RSVP”按钮以登录应用。
    • 在此 Codelab 中,您可以使用任何电子邮件地址,甚至可以使用虚假电子邮件地址,因为您没有为此 Codelab 设置电子邮件验证步骤。
    • 如果您看到错误消息“auth/operation-not-allowed”或“The given sign-in provider is disabled for this Firebase project”,请检查您是否已在 Firebase 控制台中启用电子邮件地址/密码作为登录服务提供方。
    应用预览

    此步骤的屏幕截图

  2. 前往 Firebase 控制台中的 Authentication 信息中心。在用户标签页中,您应该会看到用于登录该应用的帐号信息。

    此步骤的屏幕截图

向界面添加身份验证状态

接下来,请确保界面显示您已登录。

您将使用 Firebase Authentication 状态监听器回调,每当用户的登录状态发生变化时,该回调都会收到通知。如果当前有已登录用户,您的应用会将“回复”按钮切换为“退出”按钮。

  1. 在 StackBlitz 中,打开 index.js 文件。
  2. 在顶部找到 firebase/auth import 语句,然后添加 signOutonAuthStateChanged,如下所示:
    // ...
    // Add the Firebase products and methods that you want to use
    import {
      getAuth,
      EmailAuthProvider,
      signOut,
      onAuthStateChanged
    } from 'firebase/auth';
    
    import {} from 'firebase/firestore';
    
  3. main() 函数底部添加以下代码:
    async function main() {
      // ...
    
      // Listen to the current Auth state
      onAuthStateChanged(auth, user => {
        if (user) {
          startRsvpButton.textContent = 'LOGOUT';
        } else {
          startRsvpButton.textContent = 'RSVP';
        }
      });
    }
    main();
    
  4. 在按钮监听器中,检查当前是否存在用户并将其退出。为此,请将当前的 startRsvpButton.addEventListener 替换为以下内容:
    // ...
    // Called when the user clicks the RSVP button
    startRsvpButton.addEventListener('click', () => {
      if (auth.currentUser) {
        // User is signed in; allows user to sign out
        signOut(auth);
      } else {
        // No user is signed in; allows user to sign in
        ui.start('#firebaseui-auth-container', uiConfig);
      }
    });
    

现在,您的应用中的按钮应该会显示退出,而且当用户点击该按钮时,应切换回 RSVP

应用预览

此步骤的屏幕截图

7. 向 Cloud Firestore 写入消息

知道有用户会来访是好事,不过我们还是要为访客提供一些其他功能,供他们在应用中进行。如果他们能在留言簿中留言,结果会怎样?他们可以分享自己为何迫不及待想要见到谁,或与谁见面。

如需存储用户在应用中撰写的聊天消息,您需要使用 Cloud Firestore

数据模型

Cloud Firestore 是一种 NoSQL 数据库,存储在数据库中的数据会拆分为集合、文档、字段和子集合。您将把每条聊天消息作为文档存储在名为 guestbook 的顶级集合中。

Firestore 数据模型图,其中显示了具有多个消息文档的留言板集合

向 Firestore 添加消息

在本部分中,您将为用户添加向数据库写入新消息的功能。首先,为界面元素(消息字段和“发送”按钮)添加 HTML。然后,添加将这些元素挂接到数据库的代码。

若要添加消息字段和“发送”按钮的界面元素,请执行以下操作:

  1. 在 StackBlitz 中,打开 index.html 文件。
  2. 找到 guestbook-container,然后添加以下 HTML,以创建包含消息输入字段和发送按钮的表单。
    <!-- ... -->
    
     <section id="guestbook-container">
       <h2>Discussion</h2>
    
       <form id="leave-message">
         <label>Leave a message: </label>
         <input type="text" id="message">
         <button type="submit">
           <i class="material-icons">send</i>
           <span>SEND</span>
         </button>
       </form>
    
     </section>
    
    <!-- ... -->
    

应用预览

此步骤的屏幕截图

用户点击发送按钮会触发以下代码段。它会将消息输入字段的内容添加到数据库的 guestbook 集合中。具体来说,addDoc 方法会将消息内容添加到新文档(具有自动生成的 ID)到 guestbook 集合中。

  1. 在 StackBlitz 中,打开 index.js 文件。
  2. 在顶部找到 firebase/firestore 导入语句,然后添加 getFirestoreaddDoccollection,如下所示:
    // ...
    
    // Add the Firebase products and methods that you want to use
    import {
      getAuth,
      EmailAuthProvider,
      signOut,
      onAuthStateChanged
    } from 'firebase/auth';
    
    import {
      getFirestore,
      addDoc,
      collection
    } from 'firebase/firestore';
    
  3. 现在,我们将在 initializeApp 之后立即保存对 Firestore db 对象的引用:
    initializeApp(firebaseConfig);
    auth = getAuth();
    db = getFirestore();
    
  4. main() 函数的底部,添加以下代码。

    请注意,auth.currentUser.uid 是对 Firebase Authentication 为所有已登录用户提供自动生成的唯一 ID 的引用。
    async function main() {
      // ...
    
      // Listen to the form submission
      form.addEventListener('submit', async e => {
        // Prevent the default form redirect
        e.preventDefault();
        // Write a new message to the database collection "guestbook"
        addDoc(collection(db, 'guestbook'), {
          text: input.value,
          timestamp: Date.now(),
          name: auth.currentUser.displayName,
          userId: auth.currentUser.uid
        });
        // clear message input field
        input.value = '';
        // Return false to avoid redirect
        return false;
      });
    }
    main();
    

仅向已登录用户显示留言板

你不希望只让任何人看到邀请对象的聊天内容。要确保聊天的安全,一种方法是仅允许已登录的用户查看留言板。也就是说,对于您自己的应用,您还需要使用 Firebase 安全规则来保护数据库。(此 Codelab 稍后会详细介绍安全规则。)

  1. 在 StackBlitz 中,打开 index.js 文件。
  2. 修改 onAuthStateChanged 监听器以隐藏和显示留言板。
    // ...
    
    // Listen to the current Auth state
    onAuthStateChanged(auth, user => {
      if (user) {
        startRsvpButton.textContent = 'LOGOUT';
        // Show guestbook to logged-in users
        guestbookContainer.style.display = 'block';
      } else {
        startRsvpButton.textContent = 'RSVP';
        // Hide guestbook for non-logged-in users
        guestbookContainer.style.display = 'none';
      }
    });
    

测试发送消息

  1. 确保您已登录该应用。
  2. 输入消息,例如“您好!”,然后点击发送

此操作会将消息写入您的 Cloud Firestore 数据库。不过,您不会在实际 Web 应用中看到该消息,因为您仍需实现数据检索。接下来,您需要执行此操作。

但您可以在 Firebase 控制台中看到新添加的消息。

在 Firebase 控制台的 Firestore Database 信息中心,您应该会看到包含新添加消息的 guestbook 集合。如果您继续发送消息,您的留言簿集合将包含许多文档,如下所示:

Firebase 控制台

此步骤的屏幕截图

8. 阅读消息

同步邮件

很棒的一点是,访客可以将消息写入数据库,但目前还无法在应用中查看消息。

如需显示消息,您需要添加在数据发生更改时触发的监听器,然后创建用于显示新消息的界面元素。

您将添加用于监听来自应用新添加消息的代码。首先,在 HTML 中添加一个用于显示消息的部分:

  1. 在 StackBlitz 中,打开 index.html 文件。
  2. guestbook-container 中,添加一个 ID 为 guestbook 的新部分。
    <!-- ... -->
    
      <section id="guestbook-container">
       <h2>Discussion</h2>
    
       <form><!-- ... --></form>
    
       <section id="guestbook"></section>
    
     </section>
    
    <!-- ... -->
    

接下来,注册用于监听数据更改的监听器:

  1. 在 StackBlitz 中,打开 index.js 文件。
  2. 在顶部找到 firebase/firestore 导入语句,然后添加 queryorderByonSnapshot,如下所示:
    // ...
    import {
      getFirestore,
      addDoc,
      collection,
      query,
      orderBy,
      onSnapshot
    } from 'firebase/firestore';
    
  3. main() 函数的底部,添加以下代码以循环遍历数据库中的所有文档(留言板消息)。如需详细了解这段代码会发生什么情况,请参阅代码段下方的信息。
    async function main() {
      // ...
    
      // Create query for messages
      const q = query(collection(db, 'guestbook'), orderBy('timestamp', 'desc'));
      onSnapshot(q, snaps => {
        // Reset page
        guestbook.innerHTML = '';
        // Loop through documents in database
        snaps.forEach(doc => {
          // Create an HTML entry for each document and add it to the chat
          const entry = document.createElement('p');
          entry.textContent = doc.data().name + ': ' + doc.data().text;
          guestbook.appendChild(entry);
        });
      });
    }
    main();
    

如需监听数据库中的消息,您使用 collection 函数创建了一个针对特定集合的查询。上面的代码会监听 guestbook 集合(用于存储聊天消息的位置)中的更改。这些邮件还会按日期排序,并使用 orderBy('timestamp', 'desc') 在顶部显示最新邮件。

onSnapshot 函数接受两个参数:要使用的查询和回调函数。当与查询匹配的文档发生任何更改时,会触发回调函数。例如,消息已被删除、修改或添加。如需了解详情,请参阅 Cloud Firestore 文档

测试同步消息

Cloud Firestore 会自动即时地与订阅了该数据库的客户端同步数据。

  • 您之前在数据库中创建的消息应该显示在应用中。您可以随意撰写新消息,这些消息应该会立即显示。
  • 如果您在多个窗口或标签页中打开工作区,消息会在各个标签页中实时同步。
  • (可选)您可以尝试直接在 Firebase 控制台的数据库部分手动删除、修改或添加新消息;所有更改都应显示在界面中。

恭喜!您正在应用中读取 Cloud Firestore 文档!

应用预览

此步骤的屏幕截图

9. 设置基本安全规则

您最初将 Cloud Firestore 设置为使用测试模式,这意味着您的数据库可以执行读写操作。不过,您只应在开发的早期阶段使用测试模式。最佳实践是,您应在开发应用时为数据库设置安全规则。安全性应该是应用结构和行为不可或缺的一部分。

您可以利用安全规则来控制对数据库中的文档和集合的访问权限。借助灵活的规则语法,您可以创建规则来匹配从对整个数据库执行的所有写入操作到对特定文档执行的操作的所有匹配。

您可以在 Firebase 控制台中为 Cloud Firestore 编写安全规则:

  1. 在 Firebase 控制台的构建部分中,点击 Firestore 数据库,然后选择规则标签页(或点击此处直接进入规则标签页)。
  2. 您应该会看到以下默认安全规则(从即日起的几周后,公开访问的时间限制)。

此步骤的屏幕截图

识别集合

首先,确定应用向其写入数据的集合。

  1. 删除现有的 match /{document=**} 子句,您的规则将如下所示:
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
      }
    }
    
  2. match /databases/{database}/documents 中,找到您要保护的集合:
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /guestbook/{entry} {
         // You'll add rules here in the next step.
      }
    }
    

添加安全规则

因为您将身份验证 UID 用作每个留言板文档中的字段,所以您可以获得身份验证 UID 并验证尝试向文档写入内容的任何人具有匹配的身份验证 UID。

  1. 将读取和写入规则添加到您的规则集,如下所示:
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /guestbook/{entry} {
          allow read: if request.auth.uid != null;
          allow create:
            if request.auth.uid == request.resource.data.userId;
        }
      }
    }
    
  2. 点击发布即可部署您的新规则。现在,对于留言板,只有已登录的用户才能阅读消息(任何消息!),但您只能使用用户 ID 创建消息。我们也不允许修改或删除消息。

添加验证规则

  1. 添加数据验证,确保文档中包含所有预期字段:
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /guestbook/{entry} {
          allow read: if request.auth.uid != null;
          allow create:
          if request.auth.uid == request.resource.data.userId
              && "name" in request.resource.data
              && "text" in request.resource.data
              && "timestamp" in request.resource.data;
        }
      }
    }
    
  2. 点击发布以部署新规则。

重置监听器

由于您的应用现在仅允许经过身份验证的用户登录,因此您应将留言板 firestore 查询移到 Authentication 监听器内。否则,将出现权限错误,并且当用户退出帐号时,应用会断开连接。

  1. 在 StackBlitz 中,打开 index.js 文件。
  2. 将留言板集合 onSnapshot 监听器提取到名为 subscribeGuestbook 的新函数中。此外,请将 onSnapshot 函数的结果赋值给 guestbookListener 变量。

    Firestore onSnapshot 监听器会返回一个退订函数,您稍后可以使用该函数取消快照监听器。
    // ...
    // Listen to guestbook updates
    function subscribeGuestbook() {
      const q = query(collection(db, 'guestbook'), orderBy('timestamp', 'desc'));
      guestbookListener = onSnapshot(q, snaps => {
        // Reset page
        guestbook.innerHTML = '';
        // Loop through documents in database
        snaps.forEach(doc => {
          // Create an HTML entry for each document and add it to the chat
          const entry = document.createElement('p');
          entry.textContent = doc.data().name + ': ' + doc.data().text;
          guestbook.appendChild(entry);
        });
      });
    }
    
  3. 在下方添加一个名为 unsubscribeGuestbook 的新函数。检查 guestbookListener 变量是否不为 null,然后调用函数来取消监听器。
    // ...
    // Unsubscribe from guestbook updates
    function unsubscribeGuestbook() {
      if (guestbookListener != null) {
        guestbookListener();
        guestbookListener = null;
      }
    }
    

最后,将新函数添加到 onAuthStateChanged 回调中。

  1. if (user) 底部添加 subscribeGuestbook()
  2. else 语句底部添加 unsubscribeGuestbook()
    // ...
    // Listen to the current Auth state
    onAuthStateChanged(auth, user => {
      if (user) {
        startRsvpButton.textContent = 'LOGOUT';
        // Show guestbook to logged-in users
        guestbookContainer.style.display = 'block';
        // Subscribe to the guestbook collection
        subscribeGuestbook();
      } else {
        startRsvpButton.textContent = 'RSVP';
        // Hide guestbook for non-logged-in users
        guestbookContainer.style.display = 'none';
        // Unsubscribe from the guestbook collection
        unsubscribeGuestbook();
      }
    });
    

10. 额外步骤:练习所学内容

记录参加者的回复状态

目前,你的应用只允许对活动感兴趣的人发起聊天。此外,您知道他人是否参加的唯一方法就是他们是否在聊天中发布通知。让我们把活动整理得井井有条,让大家知道有多少人参加。

您需要添加一个切换开关,用于登记想要参加活动的用户,然后收集参与者数量。

  1. 在 StackBlitz 中,打开 index.html 文件。
  2. guestbook-container 中,添加一组 YESNO 按钮,如下所示:
    <!-- ... -->
      <section id="guestbook-container">
       <h2>Are you attending?</h2>
         <button id="rsvp-yes">YES</button>
         <button id="rsvp-no">NO</button>
    
       <h2>Discussion</h2>
    
       <!-- ... -->
    
     </section>
    <!-- ... -->
    

应用预览

此步骤的屏幕截图

接下来,注册按钮点击监听器。如果用户点击 YES,则使用身份验证 UID 将响应保存到数据库。

  1. 在 StackBlitz 中,打开 index.js 文件。
  2. 在顶部找到 firebase/firestore 导入语句,然后添加 docsetDocwhere,如下所示:
    // ...
    // Add the Firebase products and methods that you want to use
    import {
      getFirestore,
      addDoc,
      collection,
      query,
      orderBy,
      onSnapshot,
      doc,
      setDoc,
      where
    } from 'firebase/firestore';
    
  3. main() 函数的底部,添加以下代码以监听回复状态:
    async function main() {
      // ...
    
      // Listen to RSVP responses
      rsvpYes.onclick = async () => {
      };
      rsvpNo.onclick = async () => {
      };
    }
    main();
    
    
  4. 接下来,创建一个名为 attendees 的新集合,然后在用户点击任一回复按钮时注册文档引用。根据点击的是哪个按钮,设置对 truefalse 的引用。

    首先,对于 rsvpYes
    // ...
    // Listen to RSVP responses
    rsvpYes.onclick = async () => {
      // Get a reference to the user's document in the attendees collection
      const userRef = doc(db, 'attendees', auth.currentUser.uid);
    
      // If they RSVP'd yes, save a document with attendi()ng: true
      try {
        await setDoc(userRef, {
          attending: true
        });
      } catch (e) {
        console.error(e);
      }
    };
    
    然后,对 rsvpNo 进行相同操作,但值为 false
    rsvpNo.onclick = async () => {
      // Get a reference to the user's document in the attendees collection
      const userRef = doc(db, 'attendees', auth.currentUser.uid);
    
      // If they RSVP'd yes, save a document with attending: true
      try {
        await setDoc(userRef, {
          attending: false
        });
      } catch (e) {
        console.error(e);
      }
    };
    

更新安全规则

由于您已经设置了一些规则,因此使用按钮添加的新数据将被拒绝。

允许向“attendees”集合中添加内容

您需要更新这些规则,才能允许向“attendees”集合添加内容。

  1. 对于 attendees 集合,由于您使用身份验证 UID 作为文档名称,因此您可以获取该 UID 并验证提交者的 uid 是否与他们正在写入的文档相同。您将允许所有人读取参加者列表(因为其中没有私密数据),但只有创建者才能更新该列表。
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        // ... //
        match /attendees/{userId} {
          allow read: if true;
          allow write: if request.auth.uid == userId;
        }
      }
    }
    
  2. 点击发布以部署新规则。

添加验证规则

  1. 添加一些数据验证规则,确保所有预期字段都包含在文档中:
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        // ... //
        match /attendees/{userId} {
          allow read: if true;
          allow write: if request.auth.uid == userId
              && "attending" in request.resource.data;
    
        }
      }
    }
    
  2. 别忘了点击发布以部署您的规则!

(可选)您现在可以查看点击按钮后的结果。进入 Firebase 控制台中的 Cloud Firestore 信息中心。

读取回复状态

现在,您已经记录了响应,让我们看看谁会参与并在界面中反映这些反馈。

  1. 在 StackBlitz 中,打开 index.html 文件。
  2. description-container 中,添加一个 ID 为 number-attending 的新元素。
    <!-- ... -->
    
     <section id="description-container">
         <!-- ... -->
         <p id="number-attending"></p>
     </section>
    
    <!-- ... -->
    

接下来,为 attendees 集合注册监听器并统计 YES 响应的数量:

  1. 在 StackBlitz 中,打开 index.js 文件。
  2. main() 函数底部,添加以下代码以监听 RSVP 状态并统计 YES 点击次数。
    async function main() {
      // ...
    
      // Listen for attendee list
      const attendingQuery = query(
        collection(db, 'attendees'),
        where('attending', '==', true)
      );
      const unsubscribe = onSnapshot(attendingQuery, snap => {
        const newAttendeeCount = snap.docs.length;
        numberAttending.innerHTML = newAttendeeCount + ' people going';
      });
    }
    main();
    

最后,我们突出显示与当前状态对应的按钮。

  1. 创建一个函数,用于检查当前身份验证 UID 在 attendees 集合中是否有条目,然后将按钮类设置为 clicked
    // ...
    // Listen for attendee list
    function subscribeCurrentRSVP(user) {
      const ref = doc(db, 'attendees', user.uid);
      rsvpListener = onSnapshot(ref, doc => {
        if (doc && doc.data()) {
          const attendingResponse = doc.data().attending;
    
          // Update css classes for buttons
          if (attendingResponse) {
            rsvpYes.className = 'clicked';
            rsvpNo.className = '';
          } else {
            rsvpYes.className = '';
            rsvpNo.className = 'clicked';
          }
        }
      });
    }
    
  2. 此外,让我们创建一个用于退订的函数。用户退出账号后将使用此名称。
    // ...
    function unsubscribeCurrentRSVP() {
      if (rsvpListener != null) {
        rsvpListener();
        rsvpListener = null;
      }
      rsvpYes.className = '';
      rsvpNo.className = '';
    }
    
  3. 通过 Authentication 监听器调用函数。
    // ...
    // Listen to the current Auth state
      // Listen to the current Auth state
      onAuthStateChanged(auth, user => {
        if (user) {
          startRsvpButton.textContent = 'LOGOUT';
          // Show guestbook to logged-in users
          guestbookContainer.style.display = 'block';
    
          // Subscribe to the guestbook collection
          subscribeGuestbook();
          // Subscribe to the user's RSVP
          subscribeCurrentRSVP(user);
        } else {
          startRsvpButton.textContent = 'RSVP';
          // Hide guestbook for non-logged-in users
          guestbookContainer.style.display = 'none'
          ;
          // Unsubscribe from the guestbook collection
          unsubscribeGuestbook();
          // Unsubscribe from the guestbook collection
          unsubscribeCurrentRSVP();
        }
      });
    
  4. 尝试以多个用户身份登录,每当点击 YES 按钮时,计数就会增加。

应用预览

此步骤的屏幕截图

11. 恭喜!

您已使用 Firebase 构建了一个交互式实时 Web 应用!

所学内容

  • Firebase Authentication
  • FirebaseUI
  • Cloud Firestore
  • Firebase 安全规则

后续步骤

  • 想要详细了解 Firebase 开发者工作流程?请查看 Firebase 模拟器 Codelab,了解如何完全在本地测试和运行应用。
  • 想要详细了解其他 Firebase 产品?也许您想存储用户上传的图片文件?或者向用户发送通知?查看 Firebase Web Codelab,通过 Codelab 更深入地了解更多适用于 Web 的 Firebase 产品。
  • 想要详细了解 Cloud Firestore?也许您想了解子集合和事务?请访问 Cloud Firestore Web Codelab,进入一个更深入地了解 Cloud Firestore 的 Codelab。或者观看此 YouTube 系列视频,了解 Cloud Firestore

了解详情

效果如何?

我们期待您的反馈!请在此处填写一份(非常)简短的表单。