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 项目

  1. 访问 Firebase 控制台
  2. 点击 "添加项目",输入项目名称
  3. 选择或创建 Google Cloud 项目
  4. 启用或禁用 Google Analytics(可选)
  5. 完成项目创建

配置 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 app

3. 认证功能集成

邮箱密码认证

<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. 官方文档

  2. 学习资源

  3. 示例项目

  4. 社区资源

  5. 工具和扩展

课后练习

练习 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 集成的核心技能,能够构建安全、高性能的全栈应用。

« 上一篇 Vue 3与Capacitor移动端开发 下一篇 » Vue 3与Supabase后端集成 - 开源BaaS全栈解决方案