智能工具库

Firestore 数据模型与 CRUD 实战

Firestore 数据模型与 CRUD 实战

本文从零讲解 Firestore 的 NoSQL 数据组织方式,并通过任务管理应用示例,手把手演示增删改查操作,帮助开发者避开常见陷阱。

2026-09-01 0来源:freeCodeCamp

为什么需要理解 Firestore 的数据结构

很多开发者从 SQL 数据库转向 Firebase 时,容易把 Firestore 当成关系型数据库来用,结果导致数据组织混乱、查询困难。Firestore 是 Google 提供的 NoSQL 文档数据库,它的核心概念是集合(Collection)文档(Document),而不是表、行和主键。

集合就像一个命名空间,存放一组文档,比如 tasksusers。文档则是集合内的单条记录,用唯一 ID 标识,内容以键值对形式存储,类似 JSON 对象。关键区别在于:同一集合中的文档不必拥有相同字段。比如一个任务文档可能有 dueDate,另一个可能没有。Firestore 在数据库层面不强制 schema,这种灵活性由应用代码来掌控。

嵌套数据:Map 与子集合

文档支持两种嵌套方式:

  • Map:直接内嵌在文档中的对象,例如 metadata: { priority: "high", dueDate: ... }
  • 子集合:挂在某个文档下的独立集合,例如每个任务可以有自己的 comments 子集合。

这种结构形成一棵树,例如:

tasks (集合)
└── taskId (文档)
    ├── title: "写文章"
    ├── completed: false
    ├── tags: ["写作", "Firebase"]
    ├── metadata: { priority: "high" }
    └── comments (子集合)
        └── commentId (文档)
            ├── text: "写得不错"
            └── createdAt: ...

Firestore 支持多种原生数据类型,包括字符串、数字、布尔值、数组、Map、时间戳(Timestamp)、文档引用(Reference)和地理坐标(Geopoint)。掌握这些类型对后续 CRUD 操作至关重要。

CRUD 操作与数据结构的关联

理解结构后,CRUD 就变得清晰:

  • Create:向集合中添加文档,可让 Firestore 自动生成 ID,也可自定义。
  • Read:按 ID 获取单个文档,或用查询条件获取文档列表。
  • Update:修改已有文档的字段,包括嵌套 Map 和数组元素。
  • Delete:删除文档,但Firestore 不会自动清理其子集合,这是新手常踩的坑。

实战:构建任务管理应用

环境准备

需要 Node.js 18+、一个 Google 账号(Firebase 免费 Spark 计划即可)、以及基本的 JavaScript 知识(async/await、ES Modules)。无需 Firebase 或 NoSQL 经验。

第一步:创建 Firebase 项目

在 Firebase 控制台新建项目,然后注册 Web 应用,获取配置对象。

第二步:初始化 SDK

在项目中安装 Firebase Web SDK(v9+ 模块化版本),并初始化:

import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';

const app = initializeApp(yourConfig);
const db = getFirestore(app);

第三步:创建任务(Create)

使用 addDoc 添加文档,自动生成 ID:

import { collection, addDoc } from 'firebase/firestore';

const docRef = await addDoc(collection(db, 'tasks'), {
  title: '学习 Firestore',
  completed: false,
  tags: ['学习']
});
console.log('新任务 ID:', docRef.id);

第四步:查询任务(Read)

按 ID 获取单个文档:

import { doc, getDoc } from 'firebase/firestore';

const snap = await getDoc(doc(db, 'tasks', docRef.id));
if (snap.exists()) console.log(snap.data());

条件查询用 querywhere

import { query, where, getDocs } from 'firebase/firestore';

const q = query(collection(db, 'tasks'), where('completed', '==', false));
const snapshot = await getDocs(q);
snapshot.forEach(doc => console.log(doc.id, doc.data()));

第五步:更新任务(Update)

更新普通字段:

import { updateDoc } from 'firebase/firestore';

await updateDoc(doc(db, 'tasks', docRef.id), { completed: true });

更新嵌套 Map 字段,使用点号路径:

await updateDoc(docRef, { 'metadata.priority': 'low' });

操作数组用 arrayUnionarrayRemove

import { arrayUnion, arrayRemove } from 'firebase/firestore';

await updateDoc(docRef, { tags: arrayUnion('firebase') });
await updateDoc(docRef, { tags: arrayRemove('学习') });

第六步:删除任务(Delete)

删除文档本身:

import { deleteDoc } from 'firebase/firestore';
await deleteDoc(doc(db, 'tasks', docRef.id));

注意:如果该文档有子集合(如 comments),它们不会被自动删除。你需要遍历子集合并逐个删除,或使用批量写入(writeBatch)来确保数据一致性。

常见问题与调试技巧

  • 权限错误:默认测试环境可能要求设置 Firestore 安全规则,在控制台调整规则允许读写。
  • 数据类型不匹配:检查时间戳是否使用 Timestamp.now(),而非普通 Date 对象。
  • 查询性能:避免跨文档的复杂关联,Flatten 数据或使用子集合设计。

总结

Firestore 的 NoSQL 模型看似简单,但真正理解集合、文档、嵌套结构后,才能写出高效、可维护的代码。建议开发者先规划数据树,再动手写 CRUD,尤其注意删除操作对子集合的影响。通过本文的任务管理示例,你应该能独立完成基本的数据操作了。

本文基于 freeCodeCamp 的公开内容,由 AI 辅助整理改写后发布。

原标题:How Firestore Structures Data and How to Perform CRUD Operations With It

阅读原文