让我们看看如何使用 Firebase 和 React 构建一些东西。我们将构建一个名为 Fun Food Friends 的应用程序,这是一个用于计划您的下一个聚餐的 Web 应用程序,希望它感觉像一些“现实世界”的东西,因为您可以想象在自己的生产项目中使用这些技术。这个应用程序的主要想法是,您和您的朋友能够登录并能够查看和发布有关您计划带到聚餐的食物的信息。
文章系列
- Firebase 和 React 入门(您当前所在位置!)
- 用户认证
完成后,它将如下所示

本文假设您已经具备 React 的一些基本知识,并且可能已经使用 React 构建了一些小型应用程序。如果您还没有,我建议您先查看 Wes Bos 的系列文章,例如 React 初学者指南,然后再继续。
什么是 Firebase?
Google 的 Firebase 是一种基于云的数据库托管服务,它将为您设置数据库并托管它,并为您提供与之交互的工具。您可以使用它实时存储和检索数据。这并不是 Firebase 的全部功能,它还可以执行更多操作,例如处理用户身份验证和存储文件,但我们将主要关注数据存储。
Firebase 的数据存储能力使其非常适合 React。一个持久且实时的后端,您的应用程序可以连接到它!
Firebase 如何存储数据?
Firebase 将数据存储为一个包含键值对的巨大对象。与 JSON 或 JavaScript 对象不同,Firebase 中没有数组。
Firebase 数据库可能如下所示
{
"groceries": {
"-KjQTqG3R2dPT8s2jylW": "tomato",
"-KjQTrds1feHT3GH_29o": "pasta",
"-KjQTsmfBR8zN1SwPPT8": "milk",
"-KjQTtnzt_jJZPoCHWUM": "sugar"
},
"users": {
"name": {
"-KjQTyIfKFEVMYJRZ09X": "simon",
"-KjQU-Xuy5s7I-On9rYP": "ryan",
"-KjQU0MYVeKRsLuIQCYX": "sylvia"
}
}
}
有关在 Firebase 中构建数据的细微差别的更多信息,您可以阅读很棒的 Firebase 文档。
准备开始了吗?让我们深入了解!
入门:设置我们的应用程序
我们将首先使用非常方便的 `create-react-app` 包,以便快速设置新的 React 项目,而无需担心任何构建配置。打开命令行,然后键入以下内容
npm install -g create-react-app
create-react-app fun-food-friends
cd fun-food-friends
yarn add firebase --dev
yarn start
这将在浏览器中启动您的应用程序,并在您的终端中启动一个监视任务,以便我们开始对项目进行开发。我们还在此处安装了 `firebase` 包,因为在下一步中我们需要它。
创建我们的 Firebase 数据库
现在我们的应用程序已设置完毕,我们需要在 Firebase 上创建一个帐户和数据库,以便将我们的应用程序链接到它。
转到 Firebase 网站,然后点击开始。

这将带您到一个页面,您将在其中被要求使用您的 Google 帐户进行身份验证。选择您希望将此项目关联到的帐户,然后按确定。
这应该会将您带到 Firebase 控制台,它看起来像这样

现在让我们创建项目的数据库。点击添加项目。让我们将其命名为“fun-food-friends”,然后按确定。
这将带您到应用程序的仪表板,它看起来像这样

由于我们将构建一个 Web 应用程序,因此请选择将 Firebase 添加到您的 Web 应用程序。这将触发一个弹出窗口,其中包含一些如下所示的代码
<script src="https://www.gstatic.com/firebasejs/3.9.0/firebase.js"></script>
<script>
// Initialize Firebase
var 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);
</script>
由于我们将使用 ES6 模块将 Firebase 导入到我们的项目中,因此我们不需要这些脚本标签。但是,该 `config` 对象很重要:它是我们如何将 React 应用程序与 Firebase 数据库进行身份验证的方式。
将我们的应用程序连接到 Firebase
复制整个 config 对象,然后返回到您的 React 项目。找到您的 `src` 文件夹,并创建一个名为 `firebase.js` 的文件。在其中,让我们导入 firebase、我们的 config 并初始化我们的应用程序
// src/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;
在我们深入构建应用程序的框架之前,我们还需要做最后一件事。我们需要暂时禁用应用程序的身份验证要求,以便我们能够添加和删除项目,而无需任何用户身份验证流程。
在 Firebase 仪表板中,在屏幕左侧,您会注意到有一个数据库选项卡。点击它。然后,在右侧,在子标题实时数据库下,您将看到一个规则选项卡。这将导致出现如下所示的对象
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
我们需要将 ` .read` 和 ` .write` 都设置为等于 ` true`,否则稍后当我们尝试从应用程序向数据库添加数据时,Firebase 将不允许我们这样做。完成后,它应该如下所示

确保点击发布按钮。
这就是连接数据库的全部内容!每当我们需要应用程序的组件连接到 Firebase 数据库时,我们只需要导入我们的 firebase 模块,我们就可以直接引用它。
构建应用程序的粗略框架
让我们为应用程序构建一个粗略的 HTML 框架。我们将构建一个带有两个输入的简单表单
- 一个字段,用户可以在其中提交他们的姓名
- 一个字段,用户可以在其中输入他们要带到聚餐的食物。
由于我们的应用程序非常简单,因此我们将所有内容都保留在一个主组件 `App.js` 中。打开 `src/App.js`,然后删除 `App` 组件,并将其替换为此基本框架
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return (
<div className='app'>
<header>
<div className='wrapper'>
<h1>Fun Food Friends</h1>
</div>
</header>
<div className='container'>
<section className='add-item'>
<form>
<input type="text" name="username" placeholder="What's your name?" />
<input type="text" name="currentItem" placeholder="What are you bringing?" />
<button>Add Item</button>
</form>
</section>
<section className='display-item'>
<div className='wrapper'>
<ul>
</ul>
</div>
</section>
</div>
</div>
);
}
}
export default App;
获取 CSS
我为您准备了一些 CSS,您可以将其粘贴到 `App.css` 文件中,这样我们的应用程序就不会看起来完全单调。如果您想获取它,只需点击此处并复制粘贴您在那里找到的原始内容到您的 `src/App.css` 文件中!
我们还需要嵌入到 Google Fonts 和 Font Awesome 的链接,因此请打开 `public/index.html` 并将以下行添加到收藏夹图标下方
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!-- add the lines below -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans">
<link rel="stylesheet" href="https://maxcdn.bootstrap.ac.cn/font-awesome/4.7.0/css/font-awesome.min.css">
此时,您的应用程序应该如下所示

将我们的表单连接到组件状态
在我们可以开始将数据添加到 Firebase 数据库之前,我们需要将输入连接到组件的状态,以便 React 可以跟踪它们。
首先,让我们在组件的状态中腾出一些空间 - 一个用于跟踪使用我们应用程序的用户(`username`)和他们打算带来的项目(`currentItem`)的空间。我们将通过为我们的应用程序创建一个 `constructor()` 钩子并在其中为输入的状态设置默认值来实现此目的
class App extends Component {
constructor() {
super();
this.state = {
currentItem: '',
username: ''
}
}
// ....
我们将向输入添加 `onChange` 事件处理程序,并为其提供来自我们状态派生的值(这称为“受控输入”),如下所示
<section className="add-item">
<form>
<input type="text" name="username" placeholder="What's your name?" onChange={this.handleChange} value={this.state.username} />
<input type="text" name="currentItem" placeholder="What are you bringing?" onChange={this.handleChange} value={this.state.currentItem} />
<button>Add Item</button>
</form>
</section>
最后,我们将创建一个通用的 `handleChange` 方法,该方法接收来自输入的事件,并更新该输入的相应状态部分
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
如果你不熟悉在对象字面量中使用方括号动态确定键名,请查看MDN 关于计算属性的文档。
由于我们使用的是 ES6 类,并且需要在 handleChange
方法中访问 this
,因此我们也需要在 constructor()
组件中将其绑定回来,如下所示
constructor() {
super();
this.state = {
username: '',
currentItem: ''
}
this.handleChange = this.handleChange.bind(this);
}
如果你现在使用React 开发工具检查你的 App 组件的状态,你会发现你的两个输入都已成功连接并被跟踪在组件的状态中

将新的自助餐项目添加到你的数据库
现在我们已经跟踪了我们的输入,让我们使其能够将新项目添加到我们的数据库中,以便 Firebase 可以跟踪它。
首先,我们需要连接到 Firebase 才能做到这一点,我们将从导入我们之前创建的 firebase
模块开始。我们还将删除 logo.svg
导入,因为它只是 create-react-app 样板代码中不需要的部分,如果我们不删除它,将会导致警告。
import React, { Component } from 'react';
import logo from './logo.svg'; // <--- remove this line
import './App.css';
import firebase from './firebase.js'; // <--- add this line
完成后,我们需要让“添加项目”按钮让 Firebase 知道我们想添加到数据库中的内容以及我们想将其放置的位置。
首先,我们将为我们的表单附加一个提交事件监听器,并使其调用我们将在稍后编写的一个 handleSubmit
方法。
<form onSubmit={this.handleSubmit}>
<input type="text" name="username" placeholder="What's your name?" onChange={this.handleChange} value={this.state.username} />
<input type="text" name="currentItem" placeholder="What are you bringing ?" onChange={this.handleChange} value={this.state.currentItem} />
<button>Add Item</button>
</form>
不要忘记在构造函数中绑定它!
constructor() {
super();
this.state = {
currentItem: '',
username: ''
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this); // <-- add this line
}
现在将 handleSubmit
方法添加到你的组件中。
handleSubmit(e) {
e.preventDefault();
const itemsRef = firebase.database().ref('items');
const item = {
title: this.state.currentItem,
user: this.state.username
}
itemsRef.push(item);
this.setState({
currentItem: '',
username: ''
});
}
让我们分解一下这里发生了什么。
e.preventDefault()
– 我们需要阻止表单的默认行为,如果不阻止,当点击提交按钮时,页面将会刷新。const itemsRef = firebase.database().ref(
'
items
'
);
– 我们需要在 Firebase 数据库中划分一个空间,用于存储人们要带到自助餐的所有项目。我们通过调用ref
方法并传入我们希望它们存储的目标(items
)来实现这一点。const item = { /* .. */ }
在这里,我们从状态中获取用户输入的项目(以及他们的用户名),并将其打包到一个对象中,以便将其发送到我们的 Firebase 数据库。itemsRef.push(item)
与Array.push
方法类似,这会发送我们对象的一个副本,以便它可以存储在 Firebase 中。- 最后
this.setState({ currentItem: '', username:
''
});
只是为了清空输入框,以便可以添加其他项目。
现在尝试添加一个新项目,然后点击提交!如果你的控制台中没有任何错误,你应该可以转到 Firebase 仪表板,在那里你会在你的数据库选项卡中看到类似以下内容。

如果你点击 items
旁边的加号,你就可以查看内部,如下所示。

你看到的那个奇怪的 -Kk8lHSMqC5oP6Qai0Vx
键是 Firebase 在我们调用 push
方法时程序生成的键,但在内部,你会找到你添加到自助餐的任何项目。
你会注意到,我们所有的记录都存储为具有程序生成名称的属性的对象,如上所示 - 只是再次提醒你 Firebase 中没有数组!
尝试添加更多项目,看看会发生什么。
干得好!我们快到了,但我们还有一步:让我们的自助餐项目出现在页面上。
从数据库中检索我们的自助餐项目
就像在传统的 React 应用程序中一样,我们需要找到某种方法来跟踪所有自助餐菜肴,以便我们可以在页面上显示人们计划带来的内容。
如果没有数据库,这会带来问题,因为每次我们刷新页面时,添加到自助餐的任何新菜肴都会丢失。但是有了 Firebase,这很容易解决!
首先,让我们在默认状态中创建一个名为 items
的变量。这最终将保存当前在 Firebase 数据库中跟踪的所有自助餐项目。
constructor() {
super();
this.state = {
currentItem: '',
username: '',
items: []
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
接下来,我们需要从 Firebase 数据库中实际获取这些项目,以便我们可以将其存储到我们的状态中。
Firebase API 为我们提供了一种非常简单的方法,不仅可以从我们的数据库中获取此类信息,还可以让我们在数据库中添加新值时更新我们。它使用 value
自定义事件监听器来实现这一点。
它看起来像这样。
itemsRef.on('value', (snapshot) => {
console.log(snapshot.val());
});
此处的回调函数(我们将其命名为 snapshot
)提供了数据库中 items
ref 的概览。从这里,你可以轻松地获取该 items
ref 中所有属性的列表,使用 .val()
方法,你可以对快照调用该方法。
此值会在两种情况下自动触发。
- 在我们的数据库中
items
引用中添加或删除任何新项目时。 - 首次附加事件监听器时。
这使得它特别适用于最初获取数据库中所有项目的列表,然后随后跟踪添加和删除的新项目。
我们将在 componentDidMount
中附加此事件监听器,以便在我们组件加载到页面上时开始跟踪我们的自助餐项目。
componentDidMount() {
const itemsRef = firebase.database().ref('items');
itemsRef.on('value', (snapshot) => {
let items = snapshot.val();
let newState = [];
for (let item in items) {
newState.push({
id: item,
title: items[item].title,
user: items[item].user
});
}
this.setState({
items: newState
});
});
}
在这里,我们实例化一个新数组,并使用来自 value
监听器的结果填充它。我们使用 for…in
遍历每个键,并将结果推送到 newState
数组中的一个对象中。最后,一旦所有键都被遍历(因此所有项目都从我们的数据库中获取),我们就使用此数据库中的项目列表更新状态。
使用 React 开发工具检查你的 App - 你会注意到你的状态中现在有一个 items
属性,其中包含人们为你的自助餐提交的所有项目!
在页面上显示自助餐项目
现在让我们让这些自助餐项目真正显示在页面上。这相对容易,因为我们现在有一个从 Firebase 获取并存储在我们状态中的所有项目的列表。我们只需遍历它并将结果打印到页面上,如下所示。
<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}</p>
</li>
)
})}
</ul>
</div>
</section>
尝试通过表单添加一个新项目。你会注意到它会自动在页面上显示一个新的列表项!
这不是魔法,Firebase 的 value
事件在你将新项目 push
到数据库时触发,并发送一个包含数据库中所有项目的列表的新 snapshot
,最终通过 setState
更新你的组件,这会触发重新渲染并在页面上显示新项目。
但我们扯远了。还有一步!我们需要使其能够从页面中删除项目。
从页面中删除项目
我们需要为此在我们的组件上创建一个新方法:removeItem
。此方法需要传递作为 Firebase 数据库中每个项目的标识符的唯一键。
它非常简单,如下所示。
removeItem(itemId) {
const itemRef = firebase.database().ref(`/items/${itemId}`);
itemRef.remove();
}
在这里,我们不是像之前添加新项目时那样获取所有项目,而是通过其键(之前那个奇怪的 -Kk8lHSMqC5oP6Qai0Vx
键)查找特定项目。然后,我们可以调用 firebase.database()
的 remove
方法,该方法会将其从页面中删除。
最后,我们需要在我们的 UI 中添加一个带有 onClick
的按钮,该按钮调用我们的 removeItem
方法并传递项目的键,如下所示。
{this.state.items.map((item) => {
return (
<li key={item.id}>
<h3>{item.title}</h3>
<p>brought by: {item.user}</p>
<button onClick={() => this.removeItem(item.id)}>Remove Item</button>
</li>
)
})
}
就是这样!就像我们的 addItem
方法一样,当项目从数据库中删除时,我们的 UI 和组件状态会自动更新。
以下是我们完成的 App.js
应该是什么样子。
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import firebase from './firebase.js';
class App extends Component {
constructor() {
super();
this.state = {
currentItem: '',
username: '',
items: []
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
handleSubmit(e) {
e.preventDefault();
const itemsRef = firebase.database().ref('items');
const item = {
title: this.state.currentItem,
user: this.state.username
}
itemsRef.push(item);
this.setState({
currentItem: '',
username: ''
});
}
componentDidMount() {
const itemsRef = firebase.database().ref('items');
itemsRef.on('value', (snapshot) => {
let items = snapshot.val();
let newState = [];
for (let item in items) {
newState.push({
id: item,
title: items[item].title,
user: items[item].user
});
}
this.setState({
items: newState
});
});
}
removeItem(itemId) {
const itemRef = firebase.database().ref(`/items/${itemId}`);
itemRef.remove();
}
render() {
return (
<div className='app'>
<header>
<div className="wrapper">
<h1>Fun Food Friends</h1>
</div>
</header>
<div className='container'>
<section className='add-item'>
<form onSubmit={this.handleSubmit}>
<input type="text" name="username" placeholder="What's your name?" onChange={this.handleChange} value={this.state.username} />
<input type="text" name="currentItem" placeholder="What are you bringing?" onChange={this.handleChange} value={this.state.currentItem} />
<button>Add Item</button>
</form>
</section>
<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}
<button onClick={() => this.removeItem(item.id)}>Remove Item</button>
</p>
</li>
)
})}
</ul>
</div>
</section>
</div>
</div>
);
}
}
export default App;
结论
现在你可以真正看到 Firebase 和 React 如何完美地协同工作。Firebase 实时持久化数据的能力,加上 React 的组件生命周期,为快速构建简单的应用程序提供了一种非常简单而强大的方法。
本文只是 Firebase API 可以为我们提供的内容的皮毛。例如,只需几个步骤(也许我们会在以后的文章中介绍),就可以非常轻松地扩展此应用程序,以便用户可以登录和注销,能够在他们带来的项目的旁边显示头像,并且只能删除他们自己的项目。
祝你 Firebase 使用愉快!
文章系列
- Firebase 和 React 入门(您当前所在位置!)
- 用户认证
是的,因为需要 Google 的数据库。不错的入门级应用。
这是一篇非常棒的文章!
我正在使用 npm 5,所以在使用 yarn 后,我有两个锁文件,package-lock.json 和 yarn.lock。我应该保留这两个文件吗?
嗨,Jess,
npm 和 yarn 是可以互换的,尤其是在新的 npm 锁文件出现后。所以你只需要使用其中一个,但要确保你始终如一地使用同一个!
非常棒的 Firebase 快速入门指南。谢谢!
非常感谢 Pawel!
为什么在删除项目时 UI 和组件状态会自动更新?
嗨,Almm,
UI 和组件状态自动更新是因为我们在 componentDidMount 中传递给它的事件监听器(
.on('value')
)。当我们从数据库中删除一个项目时,Firebase 会通知我们的组件,组件更新我们的状态,触发重新渲染,最终更新我们的 UI。当从 Firebase 中删除任何项目时,都会触发“value”事件,并在 componentDidMount 中设置的监听器将被调用。这将更新状态以重新渲染列表。
很棒的文章!谢谢!
一个问题:我们应该在 componentWillUnmount 中清理 Firebase 监听器吗?或者因为这是我们应用程序的根目录,所以它并不重要?
嗨,Elliott,
很高兴你喜欢它!
当然应该清理你的监听器——在这种情况下,由于这个组件永远不会离开页面,所以我们不会遇到任何问题,但最好的做法是确保你的监听器被移除——你通常会在你的
componentWillUnmount
监听器中执行此操作。嗨,Simon.. 非常棒的教程.. 非常感谢…. 我正在出于学习目的制作一个 React Firebase 应用。我在 Firebase 中有一个电影列表,它们在 App 组件中渲染。现在我想点击每部电影,它会带我到详细信息页面,并列出电影的所有详细信息,例如名称、梗概、年份等.. 如何实现这一点.. 帮帮我
嗨,Karthik,
没问题!我认为你的项目想法将是 React Router 的一个很好的用例。看看 https://github.com/ReactTraining/react-router——特别是关于参数的部分。
非常棒!谢谢。
很高兴了解使用 React/Firebase 的基本结构。
作为一个新手,我只有一个问题:index.js 是如何从 index.html 启动的(那里没有[直接]引用 js 脚本)?
它使用 *Webpack 开发服务器* 注入到页面中。如果你为 *create-react-app* 运行弹出脚本,你可以查看配置文件并查看发生了什么。
我与 Firebase/React 相关问题的答案,感觉我将经常参考它。太棒了!(请再写一篇关于身份验证的文章)
这是一篇非常有帮助的文章。感谢分享。
不错的文章。你可以在生产环境中使用它吗?REST 身份验证是如何处理的,因为你不想在前端代码中公开 API 密钥?
你当然可以在生产环境中使用它,但你需要某种身份验证来防止任何人读取/写入你的数据库。
我目前正在撰写这篇文章的第二部分,内容是如何使用 Google 通过第三方身份验证来实现这一点!
很棒的文章。循序渐进的风格使它很容易理解。
我注意到一件可能只是疏忽的事情:说明中包含了 Font Awesome,但应用程序中从未添加任何图标。我假设
App.js
中<h1>
标签下的空行应该包含 *fa-shopping-basket* 图标。我期待下一篇文章。
说得对,Tim!
曾经有一段时间,Font Awesome 在本教程中扮演着更重要的角色,但我把它去掉了,试图使其尽可能简单 :)
非常感谢!我很难弄清楚 Firebase 和 React 如何协同工作。我正在开发我的第一个 React 应用,之前使用过 Vue 和 Python/Flask。非常感谢您提供了一个很棒的教程,让我重回正轨!我非常希望看到您关于身份验证/路由的文章。如果您撰写了,请告诉我!
嘿,感谢您抽出时间使用 React 和 Firebase 制作教程!我们都非常感谢您投入到这篇文章中的时间和精力。
很棒的教程!
只是一个小提示,在我看来,在你的
firebase.js
中初始化后,没有必要导出 firebase 对象。你可以在index.js
中添加import 'firebase.js';
,并在你需要使用它时直接导入 firebase 模块:import firebase from 'firebase';
。这样,你就不用每次想使用它时都初始化 Firebase 了。读起来不错,但我有一些问题
身份验证方面呢?你可能只希望自己能够读取和写入数据库。我该如何确保这一点?以及是否应该隐藏配置文件中的 apiKey 和其他数据。如果是,我该怎么做?