这是 CSS-Tricks 文章 Firebase 和 React 入门 的后续文章。在那个教程中,我们构建了 Fun Food Friends,一个用于计划您下次聚餐的应用程序。它看起来像这样

如果您尚未完成那篇文章,请在尝试本文之前 先完成那篇文章 - 它建立在该应用程序的现有代码基础上。
如果您想跳过那篇文章并直接进入本文,您可以 克隆此仓库,其中包含第一部分应用程序的完成版本。请不要忘记,您需要创建自己的 Firebase 数据库并替换其中的凭据,并在开始之前运行 npm install
!如果您不确定如何执行这些操作中的任何一项,请在深入本文之前查看第一部分。
文章系列
- Firebase 和 React 入门
- 用户认证(您当前所在位置!)
我们将构建什么
今天,我们将向我们的 Fun Food Friends 应用程序添加身份验证,以便只有登录的用户才能查看谁在为聚餐带来什么,以及能够贡献他们自己的物品。未登录时,它看起来像这样

当用户未登录时,他们将无法查看人们为聚餐带来了什么,也无法添加他们自己的物品。
登录后,它看起来像这样

您的姓名将自动添加到添加物品部分,您的 Google 照片将显示在屏幕的右下角。您也只会能够删除您添加到聚餐的物品。
在开始之前:获取 CSS
为了给应用程序添加一些润色,我在此项目中添加了一些额外的 CSS。从这里获取并将其粘贴到 `src/App.css` 中!
入门:在我们的 Firebase 项目中启用 Google 身份验证
首先登录 Firebase 控制台 并访问数据库的仪表板。然后点击身份验证选项卡。您应该会看到类似以下内容

点击登录方法选项卡

Firebase 可以通过要求用户输入电子邮件和密码来处理身份验证,或者它可以利用 Google 和 Twitter 等第三方提供商来为您处理身份验证和身份验证流程。还记得您第一次登录 Firebase 时,它使用您的 Google 凭据来验证您的身份吗?Firebase 允许您将此功能添加到您构建的应用程序中。
我们将使用 Google 作为此项目的身份验证提供商,主要是因为它将使处理我们的身份验证流程变得非常简单:我们不必担心错误处理和密码验证之类的事情,因为 Google 将为我们处理所有这些。我们也不必构建任何 UI 组件(登录和注销按钮除外)来处理身份验证。所有内容都将通过弹出窗口进行管理。

将鼠标悬停在 Google 上,选择屏幕右侧的铅笔,然后在出现的框中点击启用。最后,点击保存。
现在,点击屏幕左侧的数据库,然后转到规则面板。它现在应该看起来像这样

在我们 Fun Food Friends 应用程序的第一个版本中,任何人都可以读取和写入我们的数据库。我们将更改此设置,以便只有登录的用户才能写入数据库。更改您的规则使其看起来像这样,然后点击发布
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
这些规则告诉 Firebase 仅允许经过身份验证的用户从数据库读取和写入。
准备我们的应用程序以添加身份验证
现在我们需要返回到我们的 `firebase.js` 文件并更新我们的配置,以便我们能够使用 Google 作为我们的第三方身份验证提供商。现在您的 `firebase.js` 应该看起来像这样
import firebase from 'firebase'
const config = {
apiKey: "AIzaSyDblTESEB1SbAVkpy2q39DI2OHphL2-Jxw",
authDomain: "fun-food-friends-eeec7.firebaseapp.com",
databaseURL: "https://fun-food-friends-eeec7.firebaseio.com",
projectId: "fun-food-friends-eeec7",
storageBucket: "fun-food-friends-eeec7.appspot.com",
messagingSenderId: "144750278413"
};
firebase.initializeApp(config);
export default firebase;
在 export default firebase
之前,添加以下内容
export const provider = new firebase.auth.GoogleAuthProvider();
export const auth = firebase.auth();
这导出 Firebase 的 auth 模块以及 Google Auth Provider,以便我们能够在应用程序的任何位置使用 Google 身份验证进行登录。
现在我们准备开始添加身份验证了!让我们转到 `app.js`。首先,让我们导入 auth
模块和 Google auth 提供程序,以便我们可以在我们的应用程序组件中使用它们
更改此行
import firebase from './firebase.js';
至
import firebase, { auth, provider } from './firebase.js';
现在,在您的 App 的构造函数中,让我们首先在我们的初始状态中腾出一个空间,该空间将保存我们所有已登录用户信息。
class App extends Component {
constructor() {
super();
this.state = {
currentItem: '',
username: '',
items: [],
user: null // <-- add this line
}
在这里,我们将 user
的默认值设置为 null
,因为在初始加载时,客户端尚未通过 Firebase 进行身份验证,因此,在初始加载时,我们的应用程序应表现得好像他们未登录。
添加登录和注销
现在,让我们向我们的 render 组件添加一个登录和注销按钮,以便用户可以点击一些按钮来登录我们的应用程序
<div className="wrapper">
<h1>Fun Food Friends</h1>
{this.state.user ?
<button onClick={this.logout}>Log Out</button>
:
<button onClick={this.login}>Log In</button>
}
</div>
如果 user
的值为真值,则表示用户当前已登录,应看到注销按钮。如果 user
的值为 null,则表示用户当前已注销,应看到登录按钮。
每个按钮的 onClick
将指向我们将在组件本身中创建的两个函数,只需一秒钟:login
和 logout
。
我们还需要在构造函数中绑定这些函数,因为最终我们需要在其中调用 this.setState
,并且我们需要访问 this
constructor() {
/* ... */
this.login = this.login.bind(this); // <-- add this line
this.logout = this.logout.bind(this); // <-- add this line
}
login
方法将处理我们与 Firebase 的身份验证,它将如下所示
handleChange(e) {
/* ... */
}
logout() {
// we will add the code for this in a moment, but need to add the method now or the bind will throw an error
}
login() {
auth.signInWithPopup(provider)
.then((result) => {
const user = result.user;
this.setState({
user
});
});
}
在这里,我们调用 auth 模块中的 signInWithPopup
方法,并传入我们的 provider
(请记住,这指的是 Google Auth Provider)。现在,当您点击“登录”按钮时,它将触发一个弹出窗口,让我们可以选择使用 Google 帐户登录,如下所示

signInWithPopup
具有一个 Promise API,它允许我们对其调用 .then
并传入一个回调函数。此回调函数将提供一个 result
对象,其中包含(除其他外)一个名为 .user
的属性,该属性包含有关刚刚成功登录的用户的所有信息 - 包括他们的姓名和用户照片。然后,我们使用 setState
将其存储在状态中。
尝试登录,然后检查 React DevTools - 您将在那里看到用户!

就是您!这还将包含指向您来自 Google 的显示照片的链接,这非常方便,因为它允许我们包含一些包含已登录用户照片的 UI。
logout
方法非常简单。在组件内部的登录方法之后,添加以下方法
logout() {
auth.signOut()
.then(() => {
this.setState({
user: null
});
});
}
我们在 auth 上调用 signOut
方法,然后使用 Promise API 将用户从应用程序的状态中删除。现在 this.state.user
等于 null,用户将看到登录按钮而不是注销按钮。
跨页面刷新持久化登录
目前,每次刷新页面时,应用程序都会忘记您已登录,这有点令人沮丧。但是 Firebase 有一个事件监听器 onAuthStateChange
,它实际上可以在每次应用程序加载时检查用户上次访问您的应用程序时是否已登录。如果他们已登录,您可以自动将其重新登录。
我们将在 componentDidMount
内部执行此操作,它适用于此类副作用。
componentDidMount() {
auth.onAuthStateChanged((user) => {
if (user) {
this.setState({ user });
}
});
// ...
当用户登录时,这会检查 Firebase 数据库以查看他们是否之前已通过身份验证。如果他们已通过身份验证,我们会将他们的用户详细信息设置回状态。
更新 UI 以反映用户的登录状态
现在我们的应用程序状态已成功跟踪用户的身份验证详细信息并与我们的 Firebase 数据库同步,只剩下一步 - 我们需要将其链接到应用程序的 UI。
这样,只有登录的用户才能看到联谊会列表并能够添加新项目。当用户登录时,我们会看到他们的显示照片,他们的姓名会自动填充到“添加项目”区域,并且他们只能删除他们自己的联谊会项目。
我希望您从应用程序 render
方法中的 <header>
后开始删除之前的内容 - 每次添加一个项目会更容易。因此,您的应用程序组件的 render 方法应如下所示。
render() {
return (
<div className='app'>
<header>
<div className="wrapper">
<h1>Fun Food Friends</h1>
{this.state.user ?
<button onClick={this.logout}>Logout</button>
:
<button onClick={this.login}>Log In</button>
}
</div>
</header>
</div>
);
}
现在我们准备开始更新 UI 了。
如果已登录,则显示用户的照片,否则提示用户登录
在这里,我们将用一个大的三元运算符包装我们的应用程序。在您的标题下方。
<div className='app'>
<header>
<div className="wrapper">
<h1>Fun Food Friends</h1>
{this.state.user ?
<button onClick={this.logout}>Logout</button>
:
<button onClick={this.login}>Log In</button>
}
</div>
</header>
{this.state.user ?
<div>
<div className='user-profile'>
<img src={this.state.user.photoURL} />
</div>
</div>
:
<div className='wrapper'>
<p>You must be logged in to see the potluck list and submit to it.</p>
</div>
}
</div>
现在,当您点击登录时,您应该会看到以下内容。

显示添加项目区域并使用登录用户的登录名或电子邮件预填充
<div>
<div className='user-profile'>
<img src={this.state.user.photoURL} />
</div>
<div className='container'>
<section className='add-item'>
<form onSubmit={this.handleSubmit}>
<input type="text" name="username" placeholder="What's your name?" value={this.state.user.displayName || this.state.user.email} />
<input type="text" name="currentItem" placeholder="What are you bringing?" onChange={this.handleChange} value={this.state.currentItem} />
<button>Add Item</button>
</form>
</section>
</div>
</div>
在这里,如果存在(有时用户未设置显示名称),我们将用户名字段的 value
设置为 this.state.user.displayName
,如果不存在,则将其设置为 this.state.user.email
。这将锁定输入,并使其用户的姓名或电子邮件自动输入到添加项目字段中。
我们还将更新 handleSubmit
,因为我们不再依赖 handleChange
在状态中设置用户的姓名,而是可以直接从 this.state.user
获取。
handleSubmit(e) {
// ....
const item = {
title: this.state.currentItem,
user: this.state.user.displayName || this.state.user.email
}
// ....
}
您的应用程序现在应如下所示。

显示联谊会项目,并允许用户仅删除他们自己的项目
现在我们将添加回我们的联谊会项目列表。我们还将为每个项目添加一个检查,以查看携带该项目的用户的身份是否与当前登录的用户匹配。如果匹配,我们将为他们提供删除该项目的选项。这远非万无一失,我不会在生产应用程序中依赖它,但它是我们可以添加到应用程序中的一个很酷的小功能。
<div className='container'>
{/* .. */}
<section className='display-item'>
<div className="wrapper">
<ul>
{this.state.items.map((item) => {
return (
<li key={item.id}>
<h3>{item.title}</h3>
<p>brought by: {item.user}
{item.user === this.state.user.displayName || item.user === this.state.user.email ?
<button onClick={() => this.removeItem(item.id)}>Remove Item</button> : null}
</p>
</li>
)
})}
</ul>
</div>
</section>
</div>
我们不会为每个项目显示删除按钮,而是编写一个快速的三元运算符来检查携带特定项目的用户的身份是否与当前登录的用户匹配。如果匹配,我们将为他们提供一个删除该项目的按钮。

在这里,我可以删除意大利面沙拉,因为我已将其添加到联谊会列表中,但我无法删除土豆(谁会在联谊会上带土豆?我的妹妹,显然)。
就是这样!将身份验证添加到新的(或现有的)Firebase 应用程序非常简单。它非常简单,可以以最少的重构添加,并允许在页面刷新之间保持身份验证。
需要注意的是,这是一个简单的应用程序 - 您需要添加其他检查和平衡来存储任何类型的安全信息。但对于我们应用程序的简单目的,它非常适合!
文章系列
- Firebase 和 React 入门
- 用户认证(您当前所在位置!)
在过去几周里,围绕身份验证的内容数量惊人地高。不过,我并不抱怨,因为 Firebase 完全满足了我现在的需求。好文章!
嘿,Simon,感谢你发布了另一个关于 React 和 Firebase 的后续教程!你应该考虑做一个多部分系列或电子书,其中包含一个更详细的复杂 Web 应用程序,该应用程序以多种方式利用 Firebase。我相信一旦人们发现 firebase 和 react 是创建可扩展应用程序的最佳方法,就会有许多开发人员对此感兴趣。
祝你好运!
顺便说一句(为了节省一些输入),您可以通过以下方式编写方法来避免在构造函数中绑定所有方法……
嗨,Simon。
本教程非常棒。
我刚接触 JS/react。
我已重复使用教程中的部分内容来处理 react/firebase。
我有两个问题
1/ 在注销时,是否有任何方法可以取消授权 Google 帐户,并让用户选择不同的 Google 帐户?我尝试了 Stack Overflow 上的一些方法,但没有一个对我有用。
方法
firebase.auth().signOut().then(function() {…});
不会让你选择另一个帐户。(或者可能是我实现的方式错误)2/ 我尝试使用 React Router 渲染显示项目的组件到其路由(基本上是一个简单的页面应用程序)。
但是出现了一个问题。我并不总是在 componentDidUpdate 中调用组件的 setState 方法。(就像你在
handleSubmit(e) {…}
中所做的那样)。但是使用 React Router,控制台始终出现错误,提示我在 componentDidMount(){} 之外尝试使用 setState。
这种情况的正确方法是什么?我无法使其工作……
非常感谢。
此致。
Peter
Simon,感谢来自乌克兰的文章;)
我是一名设计师,能够逐步学习后端知识真是太棒了。
本教程非常好。我只是想知道我们如何使用其他第三方(如 Facebook)或用户自己的电子邮件和密码来验证用户身份。
查看 Firebase 身份验证,其中包含有关 FB/电子邮件身份验证等的详细信息。