firebase-firestore
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFirebase Firestore Database
Firebase Firestore Database
Status: Production Ready
Last Updated: 2026-01-25
Dependencies: None (standalone skill)
Latest Versions: firebase@12.8.0, firebase-admin@13.6.0
状态: 已就绪可用于生产环境
最后更新: 2026-01-25
依赖项: 无(独立技能)
最新版本: firebase@12.8.0, firebase-admin@13.6.0
Quick Start (5 Minutes)
快速入门(5分钟)
1. Install Firebase SDK
1. 安装Firebase SDK
bash
undefinedbash
undefinedClient SDK (web/mobile)
客户端SDK(网页/移动端)
npm install firebase
npm install firebase
Admin SDK (server/backend)
管理端SDK(服务器/后端)
npm install firebase-admin
undefinednpm install firebase-admin
undefined2. Initialize Firebase (Client)
2. 初始化Firebase(客户端)
typescript
// src/lib/firebase.ts
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
projectId: process.env.FIREBASE_PROJECT_ID,
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.FIREBASE_APP_ID,
};
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);typescript
// src/lib/firebase.ts
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
projectId: process.env.FIREBASE_PROJECT_ID,
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.FIREBASE_APP_ID,
};
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);3. Initialize Firebase Admin (Server)
3. 初始化Firebase Admin(服务器)
typescript
// src/lib/firebase-admin.ts
import { initializeApp, cert, getApps } from 'firebase-admin/app';
import { getFirestore } from 'firebase-admin/firestore';
// Initialize only once
if (!getApps().length) {
initializeApp({
credential: cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
// Replace escaped newlines in private key
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
}),
});
}
export const adminDb = getFirestore();CRITICAL:
- Never expose in client code
FIREBASE_PRIVATE_KEY - Use Admin SDK for server-side operations (bypasses security rules)
- Use Client SDK for authenticated user operations
typescript
// src/lib/firebase-admin.ts
import { initializeApp, cert, getApps } from 'firebase-admin/app';
import { getFirestore } from 'firebase-admin/firestore';
// 仅初始化一次
if (!getApps().length) {
initializeApp({
credential: cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
// 替换私钥中的转义换行符
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
}),
});
}
export const adminDb = getFirestore();关键注意事项:
- 绝不要在客户端代码中暴露
FIREBASE_PRIVATE_KEY - 服务器端操作使用Admin SDK(绕过安全规则)
- 已认证用户的操作使用客户端SDK
Core Operations
核心操作
Document CRUD (Client SDK - Modular v9+)
文档CRUD(客户端SDK - 模块化v9+)
typescript
import {
collection,
doc,
addDoc,
getDoc,
getDocs,
setDoc,
updateDoc,
deleteDoc,
query,
where,
orderBy,
limit,
serverTimestamp,
Timestamp,
} from 'firebase/firestore';
import { db } from './firebase';
// CREATE - Auto-generated ID
const docRef = await addDoc(collection(db, 'users'), {
name: 'John Doe',
email: 'john@example.com',
createdAt: serverTimestamp(),
});
console.log('Created document with ID:', docRef.id);
// CREATE - Specific ID
await setDoc(doc(db, 'users', 'user-123'), {
name: 'Jane Doe',
email: 'jane@example.com',
createdAt: serverTimestamp(),
});
// READ - Single document
const docSnap = await getDoc(doc(db, 'users', 'user-123'));
if (docSnap.exists()) {
console.log('Document data:', docSnap.data());
} else {
console.log('No such document!');
}
// READ - Collection with query
const q = query(
collection(db, 'users'),
where('email', '==', 'john@example.com'),
orderBy('createdAt', 'desc'),
limit(10)
);
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
console.log(doc.id, ' => ', doc.data());
});
// UPDATE - Merge fields (doesn't overwrite entire document)
await updateDoc(doc(db, 'users', 'user-123'), {
name: 'Jane Smith',
updatedAt: serverTimestamp(),
});
// UPDATE - Set with merge (creates if doesn't exist)
await setDoc(doc(db, 'users', 'user-123'), {
lastLogin: serverTimestamp(),
}, { merge: true });
// DELETE
await deleteDoc(doc(db, 'users', 'user-123'));typescript
import {
collection,
doc,
addDoc,
getDoc,
getDocs,
setDoc,
updateDoc,
deleteDoc,
query,
where,
orderBy,
limit,
serverTimestamp,
Timestamp,
} from 'firebase/firestore';
import { db } from './firebase';
// 创建 - 自动生成ID
const docRef = await addDoc(collection(db, 'users'), {
name: 'John Doe',
email: 'john@example.com',
createdAt: serverTimestamp(),
});
console.log('Created document with ID:', docRef.id);
// 创建 - 指定ID
await setDoc(doc(db, 'users', 'user-123'), {
name: 'Jane Doe',
email: 'jane@example.com',
createdAt: serverTimestamp(),
});
// 读取 - 单个文档
const docSnap = await getDoc(doc(db, 'users', 'user-123'));
if (docSnap.exists()) {
console.log('Document data:', docSnap.data());
} else {
console.log('No such document!');
}
// 读取 - 带查询条件的集合
const q = query(
collection(db, 'users'),
where('email', '==', 'john@example.com'),
orderBy('createdAt', 'desc'),
limit(10)
);
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
console.log(doc.id, ' => ', doc.data());
});
// 更新 - 合并字段(不会覆盖整个文档)
await updateDoc(doc(db, 'users', 'user-123'), {
name: 'Jane Smith',
updatedAt: serverTimestamp(),
});
// 更新 - 合并设置(不存在则创建)
await setDoc(doc(db, 'users', 'user-123'), {
lastLogin: serverTimestamp(),
}, { merge: true });
// 删除
await deleteDoc(doc(db, 'users', 'user-123'));Document CRUD (Admin SDK)
文档CRUD(Admin SDK)
typescript
import { adminDb } from './firebase-admin';
import { FieldValue, Timestamp } from 'firebase-admin/firestore';
// CREATE
const docRef = await adminDb.collection('users').add({
name: 'John Doe',
createdAt: FieldValue.serverTimestamp(),
});
// CREATE with specific ID
await adminDb.collection('users').doc('user-123').set({
name: 'Jane Doe',
createdAt: FieldValue.serverTimestamp(),
});
// READ
const doc = await adminDb.collection('users').doc('user-123').get();
if (doc.exists) {
console.log('Document data:', doc.data());
}
// READ with query
const snapshot = await adminDb
.collection('users')
.where('email', '==', 'john@example.com')
.orderBy('createdAt', 'desc')
.limit(10)
.get();
snapshot.forEach((doc) => {
console.log(doc.id, '=>', doc.data());
});
// UPDATE
await adminDb.collection('users').doc('user-123').update({
name: 'Jane Smith',
updatedAt: FieldValue.serverTimestamp(),
});
// DELETE
await adminDb.collection('users').doc('user-123').delete();typescript
import { adminDb } from './firebase-admin';
import { FieldValue, Timestamp } from 'firebase-admin/firestore';
// 创建
const docRef = await adminDb.collection('users').add({
name: 'John Doe',
createdAt: FieldValue.serverTimestamp(),
});
// 创建并指定ID
await adminDb.collection('users').doc('user-123').set({
name: 'Jane Doe',
createdAt: FieldValue.serverTimestamp(),
});
// 读取
const doc = await adminDb.collection('users').doc('user-123').get();
if (doc.exists) {
console.log('Document data:', doc.data());
}
// 带查询条件的读取
const snapshot = await adminDb
.collection('users')
.where('email', '==', 'john@example.com')
.orderBy('createdAt', 'desc')
.limit(10)
.get();
snapshot.forEach((doc) => {
console.log(doc.id, '=>', doc.data());
});
// 更新
await adminDb.collection('users').doc('user-123').update({
name: 'Jane Smith',
updatedAt: FieldValue.serverTimestamp(),
});
// 删除
await adminDb.collection('users').doc('user-123').delete();Real-Time Listeners (Client SDK)
实时监听器(客户端SDK)
typescript
import { onSnapshot, query, where, collection, doc } from 'firebase/firestore';
import { db } from './firebase';
// Listen to single document
const unsubscribe = onSnapshot(doc(db, 'users', 'user-123'), (doc) => {
if (doc.exists()) {
console.log('Current data:', doc.data());
}
});
// Listen to collection with query
const q = query(
collection(db, 'messages'),
where('roomId', '==', 'room-123'),
orderBy('createdAt', 'desc'),
limit(50)
);
const unsubscribeMessages = onSnapshot(q, (querySnapshot) => {
const messages: Message[] = [];
querySnapshot.forEach((doc) => {
messages.push({ id: doc.id, ...doc.data() } as Message);
});
// Update UI with messages
setMessages(messages);
});
// Handle errors
const unsubscribeWithError = onSnapshot(
doc(db, 'users', 'user-123'),
(doc) => {
// Handle updates
},
(error) => {
console.error('Listener error:', error);
// Handle permission denied, etc.
}
);
// IMPORTANT: Unsubscribe when done (React useEffect cleanup)
useEffect(() => {
const unsubscribe = onSnapshot(/* ... */);
return () => unsubscribe();
}, []);CRITICAL:
- Always unsubscribe from listeners to prevent memory leaks
- Listeners count against your concurrent connection limit
- Each listener is a WebSocket connection
typescript
import { onSnapshot, query, where, collection, doc } from 'firebase/firestore';
import { db } from './firebase';
// 监听单个文档
const unsubscribe = onSnapshot(doc(db, 'users', 'user-123'), (doc) => {
if (doc.exists()) {
console.log('Current data:', doc.data());
}
});
// 监听带查询条件的集合
const q = query(
collection(db, 'messages'),
where('roomId', '==', 'room-123'),
orderBy('createdAt', 'desc'),
limit(50)
);
const unsubscribeMessages = onSnapshot(q, (querySnapshot) => {
const messages: Message[] = [];
querySnapshot.forEach((doc) => {
messages.push({ id: doc.id, ...doc.data() } as Message);
});
// 更新UI显示消息
setMessages(messages);
});
// 处理错误
const unsubscribeWithError = onSnapshot(
doc(db, 'users', 'user-123'),
(doc) => {
// 处理更新
},
(error) => {
console.error('Listener error:', error);
// 处理权限拒绝等错误
}
);
// 重要提示:使用完毕后取消订阅(React useEffect清理函数中)
useEffect(() => {
const unsubscribe = onSnapshot(/* ... */);
return () => unsubscribe();
}, []);关键注意事项:
- 必须取消监听器订阅以避免内存泄漏
- 监听器会占用并发连接配额
- 每个监听器对应一个WebSocket连接
Query Patterns
查询模式
Compound Queries
复合查询
typescript
import { query, where, orderBy, limit, startAfter, collection } from 'firebase/firestore';
// Multiple where clauses (requires composite index)
const q = query(
collection(db, 'products'),
where('category', '==', 'electronics'),
where('price', '<=', 1000),
orderBy('price', 'asc')
);
// Range query (only one field can have inequality)
const rangeQuery = query(
collection(db, 'events'),
where('date', '>=', new Date('2025-01-01')),
where('date', '<=', new Date('2025-12-31')),
orderBy('date', 'asc')
);
// Array contains
const arrayQuery = query(
collection(db, 'posts'),
where('tags', 'array-contains', 'firebase')
);
// Array contains any (max 30 values)
const arrayAnyQuery = query(
collection(db, 'posts'),
where('tags', 'array-contains-any', ['firebase', 'google', 'cloud'])
);
// In query (max 30 values)
const inQuery = query(
collection(db, 'users'),
where('status', 'in', ['active', 'pending'])
);
// Not in query (max 10 values)
const notInQuery = query(
collection(db, 'users'),
where('status', 'not-in', ['banned', 'deleted'])
);typescript
import { query, where, orderBy, limit, startAfter, collection } from 'firebase/firestore';
// 多个where子句(需要复合索引)
const q = query(
collection(db, 'products'),
where('category', '==', 'electronics'),
where('price', '<=', 1000),
orderBy('price', 'asc')
);
// 范围查询(仅一个字段可使用不等式)
const rangeQuery = query(
collection(db, 'events'),
where('date', '>=', new Date('2025-01-01')),
where('date', '<=', new Date('2025-12-31')),
orderBy('date', 'asc')
);
// 数组包含
const arrayQuery = query(
collection(db, 'posts'),
where('tags', 'array-contains', 'firebase')
);
// 数组包含任意值(最多30个值)
const arrayAnyQuery = query(
collection(db, 'posts'),
where('tags', 'array-contains-any', ['firebase', 'google', 'cloud'])
);
// In查询(最多30个值)
const inQuery = query(
collection(db, 'users'),
where('status', 'in', ['active', 'pending'])
);
// Not in查询(最多10个值)
const notInQuery = query(
collection(db, 'users'),
where('status', 'not-in', ['banned', 'deleted'])
);Pagination with Cursors
基于游标的分页
typescript
import { query, orderBy, limit, startAfter, getDocs, collection, DocumentSnapshot } from 'firebase/firestore';
let lastVisible: DocumentSnapshot | null = null;
async function getNextPage() {
let q = query(
collection(db, 'posts'),
orderBy('createdAt', 'desc'),
limit(10)
);
if (lastVisible) {
q = query(q, startAfter(lastVisible));
}
const snapshot = await getDocs(q);
// Save last document for next page
lastVisible = snapshot.docs[snapshot.docs.length - 1] || null;
return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
}typescript
import { query, orderBy, limit, startAfter, getDocs, collection, DocumentSnapshot } from 'firebase/firestore';
let lastVisible: DocumentSnapshot | null = null;
async function getNextPage() {
let q = query(
collection(db, 'posts'),
orderBy('createdAt', 'desc'),
limit(10)
);
if (lastVisible) {
q = query(q, startAfter(lastVisible));
}
const snapshot = await getDocs(q);
// 保存最后一个文档用于下一页查询
lastVisible = snapshot.docs[snapshot.docs.length - 1] || null;
return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
}Collection Group Queries
集合组查询
typescript
import { collectionGroup, query, where, getDocs } from 'firebase/firestore';
// Query across all subcollections named 'comments'
// Structure: posts/{postId}/comments/{commentId}
const q = query(
collectionGroup(db, 'comments'),
where('authorId', '==', 'user-123')
);
const snapshot = await getDocs(q);
// Returns all comments by user-123 across all postsCRITICAL: Collection group queries require an index. Create in Firebase Console or deploy via .
firestore.indexes.jsontypescript
import { collectionGroup, query, where, getDocs } from 'firebase/firestore';
// 查询所有名为'comments'的子集合
// 结构:posts/{postId}/comments/{commentId}
const q = query(
collectionGroup(db, 'comments'),
where('authorId', '==', 'user-123')
);
const snapshot = await getDocs(q);
// 返回用户user-123在所有帖子下的评论关键注意事项: 集合组查询需要索引。在Firebase控制台创建或通过部署。
firestore.indexes.jsonBatch Operations & Transactions
批量操作与事务
Batch Writes (Up to 500 operations)
批量写入(最多500个操作)
typescript
import { writeBatch, doc, collection, serverTimestamp } from 'firebase/firestore';
import { db } from './firebase';
const batch = writeBatch(db);
// Add multiple documents
const usersRef = collection(db, 'users');
batch.set(doc(usersRef), { name: 'User 1', createdAt: serverTimestamp() });
batch.set(doc(usersRef), { name: 'User 2', createdAt: serverTimestamp() });
// Update existing document
batch.update(doc(db, 'counters', 'users'), { total: 100 });
// Delete document
batch.delete(doc(db, 'temp', 'old-doc'));
// Commit all operations atomically
await batch.commit();typescript
import { writeBatch, doc, collection, serverTimestamp } from 'firebase/firestore';
import { db } from './firebase';
const batch = writeBatch(db);
// 添加多个文档
const usersRef = collection(db, 'users');
batch.set(doc(usersRef), { name: 'User 1', createdAt: serverTimestamp() });
batch.set(doc(usersRef), { name: 'User 2', createdAt: serverTimestamp() });
// 更新现有文档
batch.update(doc(db, 'counters', 'users'), { total: 100 });
// 删除文档
batch.delete(doc(db, 'temp', 'old-doc'));
// 原子提交所有操作
await batch.commit();Transactions (Read then Write)
事务(先读后写)
typescript
import { runTransaction, doc, increment } from 'firebase/firestore';
import { db } from './firebase';
// Transfer credits between users
async function transferCredits(fromId: string, toId: string, amount: number) {
await runTransaction(db, async (transaction) => {
const fromRef = doc(db, 'users', fromId);
const toRef = doc(db, 'users', toId);
const fromDoc = await transaction.get(fromRef);
const toDoc = await transaction.get(toRef);
if (!fromDoc.exists() || !toDoc.exists()) {
throw new Error('User not found');
}
const fromCredits = fromDoc.data().credits;
if (fromCredits < amount) {
throw new Error('Insufficient credits');
}
transaction.update(fromRef, { credits: fromCredits - amount });
transaction.update(toRef, { credits: increment(amount) });
});
}CRITICAL:
- Transactions can fail and retry automatically (up to 5 times)
- Don't perform side effects inside transaction (may run multiple times)
- All reads must come before writes in a transaction
typescript
import { runTransaction, doc, increment } from 'firebase/firestore';
import { db } from './firebase';
// 用户间积分转账
async function transferCredits(fromId: string, toId: string, amount: number) {
await runTransaction(db, async (transaction) => {
const fromRef = doc(db, 'users', fromId);
const toRef = doc(db, 'users', toId);
const fromDoc = await transaction.get(fromRef);
const toDoc = await transaction.get(toRef);
if (!fromDoc.exists() || !toDoc.exists()) {
throw new Error('User not found');
}
const fromCredits = fromDoc.data().credits;
if (fromCredits < amount) {
throw new Error('Insufficient credits');
}
transaction.update(fromRef, { credits: fromCredits - amount });
transaction.update(toRef, { credits: increment(amount) });
});
}关键注意事项:
- 事务可能失败并自动重试(最多5次)
- 不要在事务内执行副作用(可能多次运行)
- 事务中所有读取操作必须在写入之前
Security Rules
安全规则
Basic Rules Structure
基础规则结构
javascript
// firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Helper functions
function isAuthenticated() {
return request.auth != null;
}
function isOwner(userId) {
return request.auth.uid == userId;
}
function isValidUser() {
return request.resource.data.keys().hasAll(['name', 'email'])
&& request.resource.data.name is string
&& request.resource.data.email is string;
}
// Users collection
match /users/{userId} {
allow read: if isAuthenticated();
allow create: if isAuthenticated() && isOwner(userId) && isValidUser();
allow update: if isOwner(userId);
allow delete: if isOwner(userId);
}
// Posts collection with subcollections
match /posts/{postId} {
allow read: if resource.data.published == true || isOwner(resource.data.authorId);
allow create: if isAuthenticated() && request.resource.data.authorId == request.auth.uid;
allow update, delete: if isOwner(resource.data.authorId);
// Comments subcollection
match /comments/{commentId} {
allow read: if true;
allow create: if isAuthenticated();
allow update, delete: if isOwner(resource.data.authorId);
}
}
// Admin-only collection
match /admin/{document=**} {
allow read, write: if request.auth.token.admin == true;
}
}
}javascript
// firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// 辅助函数
function isAuthenticated() {
return request.auth != null;
}
function isOwner(userId) {
return request.auth.uid == userId;
}
function isValidUser() {
return request.resource.data.keys().hasAll(['name', 'email'])
&& request.resource.data.name is string
&& request.resource.data.email is string;
}
// 用户集合
match /users/{userId} {
allow read: if isAuthenticated();
allow create: if isAuthenticated() && isOwner(userId) && isValidUser();
allow update: if isOwner(userId);
allow delete: if isOwner(userId);
}
// 带自集合的帖子集合
match /posts/{postId} {
allow read: if resource.data.published == true || isOwner(resource.data.authorId);
allow create: if isAuthenticated() && request.resource.data.authorId == request.auth.uid;
allow update, delete: if isOwner(resource.data.authorId);
// 评论子集合
match /comments/{commentId} {
allow read: if true;
allow create: if isAuthenticated();
allow update, delete: if isOwner(resource.data.authorId);
}
}
// 仅管理员可访问的集合
match /admin/{document=**} {
allow read, write: if request.auth.token.admin == true;
}
}
}Deploy Rules
部署规则
bash
undefinedbash
undefinedDeploy rules
部署规则
firebase deploy --only firestore:rules
firebase deploy --only firestore:rules
Deploy rules and indexes
部署规则和索引
firebase deploy --only firestore
---firebase deploy --only firestore
---Indexes
索引
Composite Indexes (firestore.indexes.json)
复合索引(firestore.indexes.json)
json
{
"indexes": [
{
"collectionGroup": "products",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "category", "order": "ASCENDING" },
{ "fieldPath": "price", "order": "ASCENDING" }
]
},
{
"collectionGroup": "comments",
"queryScope": "COLLECTION_GROUP",
"fields": [
{ "fieldPath": "authorId", "order": "ASCENDING" },
{ "fieldPath": "createdAt", "order": "DESCENDING" }
]
}
],
"fieldOverrides": []
}bash
undefinedjson
{
"indexes": [
{
"collectionGroup": "products",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "category", "order": "ASCENDING" },
{ "fieldPath": "price", "order": "ASCENDING" }
]
},
{
"collectionGroup": "comments",
"queryScope": "COLLECTION_GROUP",
"fields": [
{ "fieldPath": "authorId", "order": "ASCENDING" },
{ "fieldPath": "createdAt", "order": "DESCENDING" }
]
}
],
"fieldOverrides": []
}bash
undefinedDeploy indexes
部署索引
firebase deploy --only firestore:indexes
**CRITICAL:**
- Firestore auto-creates single-field indexes
- Composite indexes must be created manually or via error link
- Collection group queries always require an index
---firebase deploy --only firestore:indexes
**关键注意事项:**
- Firestore会自动创建单字段索引
- 复合索引必须手动创建或通过错误链接创建
- 集合组查询始终需要索引
---Offline Persistence
离线持久化
Enable Offline Support (Web)
启用离线支持(网页端)
typescript
import { initializeFirestore, persistentLocalCache, persistentMultipleTabManager } from 'firebase/firestore';
import { app } from './firebase';
// Enable multi-tab offline persistence
const db = initializeFirestore(app, {
localCache: persistentLocalCache({
tabManager: persistentMultipleTabManager()
})
});
// OR: Enable single-tab persistence (simpler)
import { enableIndexedDbPersistence, getFirestore } from 'firebase/firestore';
const db = getFirestore(app);
enableIndexedDbPersistence(db).catch((err) => {
if (err.code === 'failed-precondition') {
// Multiple tabs open, persistence can only be enabled in one tab
console.warn('Persistence failed: multiple tabs open');
} else if (err.code === 'unimplemented') {
// Browser doesn't support persistence
console.warn('Persistence not supported');
}
});typescript
import { initializeFirestore, persistentLocalCache, persistentMultipleTabManager } from 'firebase/firestore';
import { app } from './firebase';
// 启用多标签页离线持久化
const db = initializeFirestore(app, {
localCache: persistentLocalCache({
tabManager: persistentMultipleTabManager()
})
});
// 或者:启用单标签页持久化(更简单)
import { enableIndexedDbPersistence, getFirestore } from 'firebase/firestore';
const db = getFirestore(app);
enableIndexedDbPersistence(db).catch((err) => {
if (err.code === 'failed-precondition') {
// 打开了多个标签页,只能在一个标签页中启用持久化
console.warn('Persistence failed: multiple tabs open');
} else if (err.code === 'unimplemented') {
// 浏览器不支持持久化
console.warn('Persistence not supported');
}
});Handle Offline State
处理离线状态
typescript
import { onSnapshot, doc, SnapshotMetadata } from 'firebase/firestore';
onSnapshot(doc(db, 'users', 'user-123'), (doc) => {
const source = doc.metadata.fromCache ? 'local cache' : 'server';
console.log(`Data came from ${source}`);
if (doc.metadata.hasPendingWrites) {
console.log('Local changes pending sync');
}
});typescript
import { onSnapshot, doc, SnapshotMetadata } from 'firebase/firestore';
onSnapshot(doc(db, 'users', 'user-123'), (doc) => {
const source = doc.metadata.fromCache ? 'local cache' : 'server';
console.log(`Data came from ${source}`);
if (doc.metadata.hasPendingWrites) {
console.log('Local changes pending sync');
}
});Data Modeling Best Practices
数据建模最佳实践
Denormalization for Read Performance
为读取性能反规范化
typescript
// Instead of joining users and posts...
// Store author info directly in post document
// posts/{postId}
{
title: 'My Post',
content: '...',
authorId: 'user-123',
// Denormalized author data for fast reads
author: {
name: 'John Doe',
avatarUrl: 'https://...'
},
createdAt: Timestamp
}typescript
// 不要关联用户和帖子...
// 直接在帖子文档中存储作者信息
// posts/{postId}
{
title: 'My Post',
content: '...',
authorId: 'user-123',
// 反规范化的作者数据用于快速读取
author: {
name: 'John Doe',
avatarUrl: 'https://...'
},
createdAt: Timestamp
}Subcollections vs Root Collections
子集合 vs 根集合
typescript
// Subcollections: Good for parent-child relationships
// posts/{postId}/comments/{commentId}
// - Easy to query all comments for a post
// - Deleting post doesn't auto-delete comments (use Cloud Functions)
// Root collections: Good for cross-cutting queries
// comments (with postId field)
// - Easy to query all comments by a user across posts
// - Requires manual data consistencytypescript
// 子集合:适用于父子关系
// posts/{postId}/comments/{commentId}
// - 易于查询某个帖子的所有评论
// - 删除帖子不会自动删除评论(使用Cloud Functions)
// 根集合:适用于跨领域查询
// comments(包含postId字段)
// - 易于查询某个用户在所有帖子下的评论
// - 需要手动维护数据一致性Counter Pattern (High-Write Scenarios)
计数器模式(高写入场景)
typescript
// Direct increment (low traffic)
await updateDoc(doc(db, 'posts', postId), {
viewCount: increment(1)
});
// Distributed counter (high traffic - 1000+ writes/sec)
// Use Cloud Functions to aggregate shard counts
// counters/{counterId}/shards/{shardId}typescript
// 直接递增(低流量)
await updateDoc(doc(db, 'posts', postId), {
viewCount: increment(1)
});
// 分布式计数器(高流量 - 1000+次写入/秒)
// 使用Cloud Functions聚合分片计数
// counters/{counterId}/shards/{shardId}Error Handling
错误处理
Common Errors and Solutions
常见错误及解决方案
| Error | Cause | Solution |
|---|---|---|
| Security rules blocking access | Check rules, ensure user authenticated |
| Document doesn't exist | Use |
| Document with ID already exists | Use |
| Quota exceeded | Upgrade plan or optimize queries |
| Index missing for query | Create composite index (link in error) |
| Service temporarily unavailable | Implement retry with backoff |
| Invalid query combination | Check query constraints (see below) |
| Operation timeout | Reduce data size or paginate |
| 错误 | 原因 | 解决方案 |
|---|---|---|
| 安全规则阻止访问 | 检查规则,确保用户已认证 |
| 文档不存在 | 访问数据前使用 |
| 该ID的文档已存在 | 使用 |
| 配额超限 | 升级套餐或优化查询 |
| 查询缺少索引 | 创建复合索引(错误信息中的链接) |
| 服务暂时不可用 | 实现带退避策略的重试机制 |
| 查询组合无效 | 检查查询约束(见下文) |
| 操作超时 | 减少数据量或使用分页 |
Query Constraints
查询约束
typescript
// INVALID: Multiple inequality filters on different fields
query(collection(db, 'posts'),
where('date', '>', startDate),
where('likes', '>', 100) // ERROR: Can't use inequality on second field
);
// VALID: Use range on one field, equality on others
query(collection(db, 'posts'),
where('category', '==', 'tech'),
where('date', '>', startDate)
);
// INVALID: orderBy field different from inequality field
query(collection(db, 'posts'),
where('date', '>', startDate),
orderBy('likes') // ERROR: Must orderBy('date') first
);
// VALID: orderBy inequality field first
query(collection(db, 'posts'),
where('date', '>', startDate),
orderBy('date'),
orderBy('likes')
);typescript
// 无效:在不同字段上使用多个不等式过滤器
query(collection(db, 'posts'),
where('date', '>', startDate),
where('likes', '>', 100) // 错误:不能在第二个字段上使用不等式
);
// 有效:在一个字段上使用范围,其他字段使用相等
query(collection(db, 'posts'),
where('category', '==', 'tech'),
where('date', '>', startDate)
);
// 无效:orderBy字段与不等式字段不同
query(collection(db, 'posts'),
where('date', '>', startDate),
orderBy('likes') // 错误:必须先orderBy('date')
);
// 有效:先按不等式字段排序
query(collection(db, 'posts'),
where('date', '>', startDate),
orderBy('date'),
orderBy('likes')
);Known Issues Prevention
已知问题预防
This skill prevents 10 documented Firestore errors:
| Issue # | Error/Issue | Description | How to Avoid | Source |
|---|---|---|---|---|
| #1 | | Security rules blocking operation | Test rules in Firebase Console emulator first | Common |
| #2 | | Composite index missing | Click error link to create index, or define in firestore.indexes.json | Common |
| #3 | Invalid query combination | Multiple inequality filters | Use inequality on one field only, equality on others | Docs |
| #4 | Memory leak from listeners | Not unsubscribing from onSnapshot | Always call unsubscribe in cleanup (useEffect return) | Common |
| #5 | Offline persistence conflict | Multiple tabs with persistence | Use | Docs |
| #6 | Transaction side effects | Side effects run multiple times | Never perform side effects inside runTransaction | Docs |
| #7 | Batch limit exceeded | More than 500 operations | Split into multiple batches | Docs |
| #8 | | Quota limits hit | Implement pagination, reduce reads, use caching | Common |
| #9 | Private key newline issue | | Use | Common |
| #10 | Collection group query fails | Missing collection group index | Create index with | Docs |
本技能可预防10种已记录的Firestore错误:
| 问题编号 | 错误/问题 | 描述 | 避免方法 | 来源 |
|---|---|---|---|---|
| #1 | | 安全规则阻止操作 | 先在Firebase控制台模拟器中测试规则 | 常见问题 |
| #2 | | 缺少复合索引 | 点击错误链接创建索引,或在firestore.indexes.json中定义 | 常见问题 |
| #3 | 查询组合无效 | 多个不等式过滤器 | 仅在一个字段上使用不等式,其他字段使用相等 | 文档 |
| #4 | 监听器导致内存泄漏 | 未取消onSnapshot订阅 | 始终在清理函数中调用unsubscribe(如React useEffect返回) | 常见问题 |
| #5 | 离线持久化冲突 | 多个标签页开启持久化 | 使用 | 文档 |
| #6 | 事务副作用 | 副作用多次运行 | 绝不要在runTransaction内执行副作用 | 文档 |
| #7 | 批量操作超出限制 | 超过500个操作 | 拆分为多个批量操作 | 文档 |
| #8 | | 达到配额限制 | 实现分页,减少读取次数,使用缓存 | 常见问题 |
| #9 | 私钥换行符问题 | 环境变量中的 | 对私钥使用 | 常见问题 |
| #10 | 集合组查询失败 | 缺少集合组索引 | 创建 | 文档 |
Firebase CLI Commands
Firebase CLI命令
bash
undefinedbash
undefinedInitialize Firestore
初始化Firestore
firebase init firestore
firebase init firestore
Start emulators
启动模拟器
firebase emulators:start --only firestore
firebase emulators:start --only firestore
Deploy rules and indexes
部署规则和索引
firebase deploy --only firestore
firebase deploy --only firestore
Export data (for backup)
导出数据(备份)
gcloud firestore export gs://your-bucket/backups/$(date +%Y%m%d)
gcloud firestore export gs://your-bucket/backups/$(date +%Y%m%d)
Import data
导入数据
gcloud firestore import gs://your-bucket/backups/20250125
---gcloud firestore import gs://your-bucket/backups/20250125
---Package Versions (Verified 2026-01-25)
包版本(2026-01-25已验证)
json
{
"dependencies": {
"firebase": "^12.8.0"
},
"devDependencies": {
"firebase-admin": "^13.6.0"
}
}json
{
"dependencies": {
"firebase": "^12.8.0"
},
"devDependencies": {
"firebase-admin": "^13.6.0"
}
}Official Documentation
官方文档
- Firestore Overview: https://firebase.google.com/docs/firestore
- Get Started: https://firebase.google.com/docs/firestore/quickstart
- Data Model: https://firebase.google.com/docs/firestore/data-model
- Security Rules: https://firebase.google.com/docs/firestore/security/get-started
- Query Data: https://firebase.google.com/docs/firestore/query-data/queries
- Manage Data: https://firebase.google.com/docs/firestore/manage-data/add-data
- Offline Data: https://firebase.google.com/docs/firestore/manage-data/enable-offline
Last verified: 2026-01-25 | Skill version: 1.0.0
- Firestore概述: https://firebase.google.com/docs/firestore
- 快速入门: https://firebase.google.com/docs/firestore/quickstart
- 数据模型: https://firebase.google.com/docs/firestore/data-model
- 安全规则: https://firebase.google.com/docs/firestore/security/get-started
- 查询数据: https://firebase.google.com/docs/firestore/query-data/queries
- 管理数据: https://firebase.google.com/docs/firestore/manage-data/add-data
- 离线数据: https://firebase.google.com/docs/firestore/manage-data/enable-offline
最后验证: 2026-01-25 | 技能版本: 1.0.0