Vue 3 与 Firebase 后端集成
概述
Firebase 是 Google 提供的一套完整的后端即服务(BaaS)解决方案,包含认证、数据库、存储、云函数、托管等多种服务。Vue 3 与 Firebase 的结合可以让开发者快速构建现代化的全栈应用,无需管理复杂的后端基础设施。本集将详细介绍如何使用 Vue 3 和 Firebase 构建全栈应用,包括认证、数据库、存储等核心功能的集成。
核心知识点
1. Firebase 核心服务
- Firebase Authentication:提供多种认证方式(邮箱密码、Google、Facebook 等)
- Cloud Firestore:NoSQL 云数据库,支持实时数据同步
- Firebase Storage:用于存储和提供用户生成的内容(图片、视频等)
- Cloud Functions:在云端运行的无服务器函数
- Firebase Hosting:静态网站托管服务
- Cloud Messaging:推送通知服务
2. Vue 3 + Firebase 项目初始化
安装 Firebase SDK
# 安装 Firebase SDK
yarn add firebase
# 安装 VueFire(可选,Vue 官方 Firebase 集成库)
yarn add vuefire firebase创建 Firebase 项目
- 访问 Firebase 控制台
- 点击 "添加项目",输入项目名称
- 选择或创建 Google Cloud 项目
- 启用或禁用 Google Analytics(可选)
- 完成项目创建
配置 Firebase SDK
// src/firebase.js
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
import { getFirestore } from 'firebase/firestore'
import { getStorage } from 'firebase/storage'
// Firebase 配置
const firebaseConfig = {
apiKey: 'YOUR_API_KEY',
authDomain: 'YOUR_AUTH_DOMAIN',
projectId: 'YOUR_PROJECT_ID',
storageBucket: 'YOUR_STORAGE_BUCKET',
messagingSenderId: 'YOUR_MESSAGING_SENDER_ID',
appId: 'YOUR_APP_ID',
measurementId: 'YOUR_MEASUREMENT_ID' // 可选
}
// 初始化 Firebase
const app = initializeApp(firebaseConfig)
// 初始化服务
export const auth = getAuth(app)
export const db = getFirestore(app)
export const storage = getStorage(app)
export default app3. 认证功能集成
邮箱密码认证
<template>
<div class="auth-container">
<h2>注册</h2>
<form @submit.prevent="register">
<div class="form-group">
<label for="email">邮箱</label>
<input type="email" id="email" v-model="email" required />
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" v-model="password" required />
</div>
<button type="submit">注册</button>
</form>
<h2>登录</h2>
<form @submit.prevent="login">
<div class="form-group">
<label for="login-email">邮箱</label>
<input type="email" id="login-email" v-model="loginEmail" required />
</div>
<div class="form-group">
<label for="login-password">密码</label>
<input type="password" id="login-password" v-model="loginPassword" required />
</div>
<button type="submit">登录</button>
</form>
<div v-if="user" class="user-info">
<h3>当前用户</h3>
<p>{{ user.email }}</p>
<button @click="logout">退出登录</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut, onAuthStateChanged } from 'firebase/auth'
import { auth } from '../firebase'
const email = ref('')
const password = ref('')
const loginEmail = ref('')
const loginPassword = ref('')
const user = ref(null)
// 监听认证状态变化
onMounted(() => {
onAuthStateChanged(auth, (currentUser) => {
user.value = currentUser
})
})
// 注册新用户
const register = async () => {
try {
const userCredential = await createUserWithEmailAndPassword(auth, email.value, password.value)
user.value = userCredential.user
console.log('注册成功:', user.value)
} catch (error) {
console.error('注册失败:', error.message)
}
}
// 用户登录
const login = async () => {
try {
const userCredential = await signInWithEmailAndPassword(auth, loginEmail.value, loginPassword.value)
user.value = userCredential.user
console.log('登录成功:', user.value)
} catch (error) {
console.error('登录失败:', error.message)
}
}
// 用户退出
const logout = async () => {
try {
await signOut(auth)
user.value = null
console.log('退出成功')
} catch (error) {
console.error('退出失败:', error.message)
}
}
</script>Google 认证
import { GoogleAuthProvider, signInWithPopup } from 'firebase/auth'
import { auth } from '../firebase'
const googleSignIn = async () => {
try {
const provider = new GoogleAuthProvider()
const userCredential = await signInWithPopup(auth, provider)
const user = userCredential.user
console.log('Google 登录成功:', user)
} catch (error) {
console.error('Google 登录失败:', error.message)
}
}4. Cloud Firestore 集成
添加数据
import { collection, addDoc } from 'firebase/firestore'
import { db } from '../firebase'
const addPost = async (title, content) => {
try {
const docRef = await addDoc(collection(db, 'posts'), {
title,
content,
createdAt: new Date(),
author: {
id: auth.currentUser.uid,
email: auth.currentUser.email
}
})
console.log('文档已添加,ID:', docRef.id)
} catch (error) {
console.error('添加文档失败:', error.message)
}
}获取数据
<template>
<div class="posts-container">
<h2>帖子列表</h2>
<div v-for="post in posts" :key="post.id" class="post">
<h3>{{ post.title }}</h3>
<p>{{ post.content }}</p>
<p class="post-meta">作者: {{ post.author.email }} | {{ post.createdAt.toDate().toLocaleString() }}</p>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { collection, getDocs, onSnapshot } from 'firebase/firestore'
import { db } from '../firebase'
const posts = ref([])
let unsubscribe = null
// 获取所有帖子(一次性)
const fetchPosts = async () => {
try {
const querySnapshot = await getDocs(collection(db, 'posts'))
posts.value = querySnapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}))
} catch (error) {
console.error('获取帖子失败:', error.message)
}
}
// 实时监听帖子变化
const subscribeToPosts = () => {
unsubscribe = onSnapshot(collection(db, 'posts'), (querySnapshot) => {
posts.value = querySnapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}))
})
}
onMounted(() => {
// fetchPosts() // 一次性获取
subscribeToPosts() // 实时监听
})
onUnmounted(() => {
if (unsubscribe) {
unsubscribe() // 取消订阅
}
})
</script>更新和删除数据
import { doc, updateDoc, deleteDoc } from 'firebase/firestore'
import { db } from '../firebase'
// 更新帖子
const updatePost = async (postId, updates) => {
try {
const postRef = doc(db, 'posts', postId)
await updateDoc(postRef, updates)
console.log('帖子已更新')
} catch (error) {
console.error('更新帖子失败:', error.message)
}
}
// 删除帖子
const deletePost = async (postId) => {
try {
await deleteDoc(doc(db, 'posts', postId))
console.log('帖子已删除')
} catch (error) {
console.error('删除帖子失败:', error.message)
}
}5. Firebase Storage 集成
上传文件
<template>
<div class="storage-container">
<h2>文件上传</h2>
<input type="file" @change="handleFileChange" />
<button @click="uploadFile" :disabled="!file">上传</button>
<div v-if="uploadProgress > 0 && uploadProgress < 100" class="progress">
<div class="progress-bar" :style="{ width: `${uploadProgress}%` }"></div>
<span>{{ uploadProgress }}%</span>
</div>
<div v-if="downloadURL" class="file-url">
<h3>文件 URL:</h3>
<a :href="downloadURL" target="_blank">{{ downloadURL }}</a>
<img v-if="isImage" :src="downloadURL" alt="上传的图片" />
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ref as storageRef, uploadBytesResumable, getDownloadURL } from 'firebase/storage'
import { storage } from '../firebase'
const file = ref(null)
const uploadProgress = ref(0)
const downloadURL = ref('')
const isImage = ref(false)
// 处理文件选择
const handleFileChange = (e) => {
file.value = e.target.files[0]
isImage.value = file.value.type.startsWith('image/')
}
// 上传文件
const uploadFile = async () => {
if (!file.value) return
try {
// 创建存储引用
const storageReference = storageRef(storage, `uploads/${file.value.name}`)
// 创建上传任务
const uploadTask = uploadBytesResumable(storageReference, file.value)
// 监听上传进度
uploadTask.on('state_changed',
(snapshot) => {
// 计算上传进度
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
uploadProgress.value = progress
},
(error) => {
console.error('上传失败:', error.message)
},
async () => {
// 上传完成,获取下载 URL
downloadURL.value = await getDownloadURL(uploadTask.snapshot.ref)
console.log('文件已上传,URL:', downloadURL.value)
}
)
} catch (error) {
console.error('上传文件失败:', error.message)
}
}
</script>
<style scoped>
.progress {
width: 100%;
height: 20px;
background-color: #f0f0f0;
border-radius: 10px;
margin: 10px 0;
position: relative;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: #42b883;
transition: width 0.3s ease;
}
.progress span {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 12px;
color: #333;
}
.file-url img {
max-width: 200px;
max-height: 200px;
margin: 10px 0;
border-radius: 8px;
}
</style>6. VueFire 集成(可选)
安装和配置 VueFire
# 安装 VueFire
yarn add vuefire firebase// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import { VueFire, VueFireAuth } from 'vuefire'
import { firebaseApp } from './firebase'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(VueFire, {
firebaseApp,
modules: [
VueFireAuth()
]
})
app.mount('#app')使用 VueFire 组合式 API
<template>
<div class="posts-container">
<h2>帖子列表(VueFire)</h2>
<div v-for="post in posts" :key="post.id" class="post">
<h3>{{ post.title }}</h3>
<p>{{ post.content }}</p>
</div>
</div>
</template>
<script setup>
import { useCollection } from 'vuefire'
import { collection } from 'firebase/firestore'
import { db } from '../firebase'
// 使用 VueFire 获取实时数据
const postsRef = collection(db, 'posts')
const posts = useCollection(postsRef)
</script>7. Firebase 安全规则
Firestore 安全规则
// firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// 帖子集合规则
match /posts/{postId} {
// 允许所有用户读取帖子
allow read: if true;
// 只允许登录用户创建帖子
allow create: if request.auth != null;
// 只允许帖子作者更新和删除帖子
allow update, delete: if request.auth != null && request.auth.uid == resource.data.author.id;
}
// 用户集合规则
match /users/{userId} {
// 只允许用户访问自己的资料
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}Storage 安全规则
// storage.rules
service firebase.storage {
match /b/{bucket}/o {
// 允许所有用户读取文件
match /uploads/{allPaths=**} {
allow read: if true;
// 只允许登录用户上传文件
allow write: if request.auth != null;
}
}
}最佳实践
1. 认证最佳实践
- 使用多种认证方式:提供多种登录选项,提高用户体验
- 保护敏感路由:使用导航守卫保护需要认证的路由
- 处理认证状态:正确处理用户登录、注销和会话过期
- 使用自定义声明:实现基于角色的访问控制
- 验证用户输入:对所有用户输入进行验证
2. 数据库最佳实践
- 合理设计数据结构:根据查询模式设计文档结构
- 使用集合组查询:合理使用集合和子集合
- 限制查询结果:使用
limit()和分页减少数据传输 - 使用索引:为常用查询创建索引
- 批量操作:使用批量写入减少网络请求
- 事务处理:使用事务确保数据一致性
3. 存储最佳实践
- 组织文件结构:使用合理的文件路径结构
- 限制文件大小和类型:在客户端和服务器端进行验证
- 使用缓存:合理使用缓存策略减少网络请求
- 生成缩略图:为图片生成不同尺寸的缩略图
- 设置适当的访问权限:根据文件类型设置不同的访问权限
4. 性能优化
- 使用实时监听:只在需要实时数据时使用
onSnapshot - 批量更新:减少不必要的数据库写入
- 使用缓存:利用 Firebase SDK 的内置缓存
- 优化查询:使用索引和合理的查询条件
- 减少监听范围:只监听必要的数据
5. 安全最佳实践
- 最小权限原则:只授予必要的权限
- 验证所有输入:对所有客户端输入进行验证
- 使用安全规则:正确配置 Firebase 安全规则
- 加密敏感数据:对敏感数据进行加密存储
- 定期审计:定期审计安全规则和数据访问
常见问题与解决方案
1. 问题:Firebase 认证状态丢失
解决方案:
- 确保在应用启动时初始化 Firebase
- 使用
onAuthStateChanged监听认证状态变化 - 避免在组件挂载前访问
auth.currentUser - 考虑使用 VueFire 的
useCurrentUser组合式 API
2. 问题:实时数据不更新
解决方案:
- 确保使用了
onSnapshot或 VueFire 的useCollection - 检查 Firebase 安全规则是否允许读取数据
- 确保数据库中有匹配的文档
- 检查网络连接是否正常
3. 问题:文件上传失败
解决方案:
- 检查 Storage 安全规则是否允许写入
- 确保文件大小不超过 Storage 限制
- 检查文件类型是否被允许
- 检查网络连接是否正常
- 查看浏览器控制台的错误信息
4. 问题:Firebase 安全规则错误
解决方案:
- 使用 Firebase 控制台的规则模拟器测试规则
- 检查规则语法是否正确
- 确保规则逻辑符合预期
- 从宽松规则开始,逐步收紧
5. 问题:应用性能下降
解决方案:
- 减少实时监听的文档数量
- 使用
limit()和分页 - 优化查询,添加必要的索引
- 减少不必要的数据传输
- 使用缓存策略
进一步学习资源
官方文档
学习资源
示例项目
社区资源
工具和扩展
课后练习
练习 1:创建基础 Firebase 应用
- 创建 Firebase 项目
- 配置 Firebase SDK
- 实现邮箱密码认证
- 实现 Google 认证
练习 2:实现 Firestore 集成
- 创建帖子集合
- 实现帖子的增删改查
- 实现实时数据监听
- 配置 Firestore 安全规则
练习 3:实现 Storage 集成
- 实现文件上传功能
- 显示上传进度
- 显示上传后的文件(图片预览)
- 配置 Storage 安全规则
练习 4:使用 VueFire
- 安装和配置 VueFire
- 使用 VueFire 组合式 API 获取数据
- 实现认证状态管理
- 比较直接使用 Firebase SDK 和 VueFire 的差异
练习 5:构建完整应用
- 创建一个包含认证、数据库、存储的完整应用
- 实现用户资料管理
- 实现帖子评论功能
- 实现文件上传和显示
- 部署到 Firebase Hosting
练习 6:性能优化
- 优化数据库查询,添加索引
- 减少实时监听的范围
- 实现分页加载
- 测试应用性能
练习 7:安全配置
- 配置严格的安全规则
- 使用模拟器测试规则
- 实现基于角色的访问控制
- 审计数据访问
通过以上练习,你将掌握 Vue 3 与 Firebase 集成的核心技能,能够构建安全、高性能的全栈应用。