- 开发工具
- 后端
【免费下载链接】firebase-js-sdk
Firebase Javascript SDK
本篇技术指南聚焦 firebase-js-sdk 仓库中@firebase/database模块的核心类Database(即 Firebase Realtime Database 的模块化 SDK 实例)。通过阅读本文,你将掌握Database类的类签名与两个公开属性app、type的含义,理解getDatabase()如何创建并缓存实例、Database与底层Repo的连接关系,以及goOffline()/goOnline()、connectDatabaseEmulator()等配套函数如何作用于该实例,并能在实际项目中正确初始化、复用与销毁 Realtime Database 实例。
Database 类的定位:Realtime Database 的模块化入口
Database类在官方 API 参考文档 database.database.md 中被定义为 “Class representing a Firebase Realtime Database”,即一个代表 Firebase Realtime Database 实例的类。其完整类签名如下:
export declare class Database在仓库源码 packages/database/src/api/Database.ts#L235-L285 中,该类的实际声明为:
export class Database implements _FirebaseService { /** Represents a `Database` instance. */ readonly 'type' = 'database'; /** Track if the instance has been used (root or repo accessed) */ _instanceStarted: boolean = false; /** Backing state for root_ */ private _rootInternal?: ReferenceImpl; /** @hideconstructor */ constructor( public _repoInternal: Repo, /** The FirebaseApp associated with this Realtime Database instance. */ readonly app: FirebaseApp ) {} // ... }从源码可以看出两个关键事实:
Database实现了_FirebaseService接口,这意味着它是作为 Firebase App 的一个“服务组件”存在,由 Firebase 的组件注册机制统一管理(详见后文“组件注册机制”一节);- 它的构造函数带有
@hideconstructor标记,不应由开发者直接new创建,而应通过getDatabase()工厂函数获取实例。
公开属性一览:app 与 type
官方文档为Database定义了恰好两个公开属性,下表完整列出:
| 属性 | 修饰符 | 类型 | 说明 |
|---|---|---|---|
| app | readonly | FirebaseApp | 与该 Realtime Database 实例关联的FirebaseApp。 |
| type | readonly | (未声明,字面量类型) | 表示这是一个Database实例。 |
Database.app
源码声明:
readonly app: FirebaseApp;app属性在构造函数中注入(constructor(public _repoInternal: Repo, readonly app: FirebaseApp)),用于保存创建该Database实例时所关联的FirebaseApp。它在整个实例生命周期内不可变更(readonly)。你可以通过它反向访问 Firebase 应用的配置,例如db.app.options.databaseURL、db.app.options.projectId——这些配置在repoManagerDatabaseFromApp()中正是用于解析数据库地址(见后文“实例的创建路径”)。
Database.type
源码声明:
readonly 'type' = 'database';type是一个字面量类型常量属性,值为字符串'database'。它的作用是为框架层提供运行时类型判别信息:当 Firebase 的组件注册表需要识别某个服务实例的类型时,可以通过instance.type === 'database'进行校验。类似的type字段在 Firebase 其他服务的实例上也普遍存在,是模块化 SDK 中常见的实例标识模式。
获取 Database 实例:getDatabase() 工厂函数
由于构造函数被隐藏,获取Database实例的唯一标准途径是调用getDatabase():
export declare function getDatabase(app?: FirebaseApp, url?: string): Database;参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
app | FirebaseApp(可选) | 关联的 FirebaseApp 实例,省略时默认使用默认 App(getApp())。 |
url | string(可选) | 要连接的 Realtime Database 实例 URL;省略时 SDK 连接该 App 的默认实例。 |
对应源码位于 packages/database/src/api/Database.ts#L323-L337:
export function getDatabase( app: FirebaseApp = getApp(), url?: string ): Database { const db = _getProvider(app, 'database').getImmediate({ identifier: url }) as Database; if (!db._instanceStarted) { const emulator = getDefaultEmulatorHostnameAndPort('database'); if (emulator) { connectDatabaseEmulator(db, ...emulator); } } return db; }典型用法:
import { initializeApp } from 'firebase/app'; import { getDatabase, ref } from 'firebase/database'; const app = initializeApp({ databaseURL: 'https://my-app-default-rtdb.firebaseio.com' }); const db = getDatabase(app); // 默认数据库实例 const db2 = getDatabase(app, 'https://my-app-other-rtdb.firebaseio.com'); // 指定 URL 的独立实例需要特别注意两点:
url参数即组件标识符:getDatabase()通过_getProvider(app, 'database').getImmediate({ identifier: url })获取实例,url被用作组件实例的instanceIdentifier。这意味着同一个 App 下,不同 URL 会创建不同的Database实例;而对同一 URL 重复调用则返回已缓存的同一实例。- 重复初始化保护:在底层
repoManagerCreateRepo()中,如果同一 App + 同一 URL 对应的Repo已存在,会抛出错误 “Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.”(见 packages/database/src/api/Database.ts#L200-L223),因此请保证对同一数据库只使用一致的 URL 格式调用一次。
实例的创建路径:repoManagerDatabaseFromApp
getDatabase()最终调用的是组件工厂函数repoManagerDatabaseFromApp()(packages/database/src/api/Database.ts#L118-L177),其流程可以概括为:
- 确定数据库地址:优先使用显式传入的
url,其次使用app.options.databaseURL;若两者都缺失,则回退为${projectId}-default-rtdb.firebaseio.com;若连projectId也没有,直接抛出致命错误 “Can't determine Firebase Database URL…”。 - 解析 URL:通过
parseRepoInfo()将 URL 解析为RepoInfo(host、namespace、是否使用 SSL 等信息,见 packages/database/src/core/RepoInfo.ts#L32-L99)。 - 模拟器自动切换:若进程环境中存在
FIREBASE_DATABASE_EMULATOR_HOST环境变量,会优先切换为http://${dbEmulatorHost}?ns=${namespace}形式的模拟器地址。 - 校验 URL:
validateUrl()校验 URL 合法性,且要求 URL 必须指向数据库根节点(“Database URL must point to the root of a Firebase Database (not including a child path).”)。 - 创建 Repo 并返回实例:调用
repoManagerCreateRepo()创建/获取Repo,再return new Database(repo, app)。
从中可以明确Database与Repo的关系:Database是面向开发者的轻量门面(facade),而真正负责网络连接、数据同步、查询派发等核心逻辑的是内部的Repo。Database._repoInternal保存的就是这个Repo。
Database 的内部结构与生命周期管理
除了两个公开属性,Database类还包含一组带下划线前缀的内部成员(不属于公开 API,但理解它们有助于把握实例生命周期):
| 内部成员 | 类型 | 作用 |
|---|---|---|
_repoInternal | Repo | 底层 Repo,负责与服务器通信与数据同步。 |
_instanceStarted | boolean | 标记实例是否已启动(首次访问_repo时置为true)。 |
_rootInternal | ReferenceImpl | 根节点引用(Reference),首次访问_root时创建。 |
_delete() | Promise<void> | 删除实例并断开底层 Repo 连接。 |
_checkNotDeleted(apiName) | void | 在已删除实例上调用 API 时抛出致命错误。 |
延迟启动机制
_repogetter 体现了“懒启动”设计(packages/database/src/api/Database.ts#L252-L262):
get _repo(): Repo { if (!this._instanceStarted) { repoStart( this._repoInternal, this.app.options.appId, this.app.options['databaseAuthVariableOverride'] ); this._instanceStarted = true; } return this._repoInternal; }只有在第一次真正访问_repo或_root(例如调用ref(db)、建立监听)时,repoStart()才会被触发。repoStart()(packages/database/src/core/Repo.ts#L211-L284)负责创建PersistentConnection(或测试场景下的ReadonlyRestClient)、挂接认证 token 变更监听与 App Check token 监听,从而正式建立与服务器的连接。
_rootgetter 则在首次访问时创建指向根路径(空路径)的ReferenceImpl:
get _root(): ReferenceImpl { if (!this._rootInternal) { this._rootInternal = new ReferenceImpl(this._repo, newEmptyPath()); } return this._rootInternal; }删除与防误用
_delete()会调用repoManagerDeleteRepo()(packages/database/src/api/Database.ts#L183-L191),后者执行repoInterrupt(repo)断开连接,并从缓存的repos表中移除该 Repo;- 删除之后,
_checkNotDeleted()会拦截所有后续操作并抛出 “Cannot call … on a deleted database.” 致命错误。
组件注册机制:Database 如何挂载到 Firebase App
Database作为服务组件,通过 packages/database/src/register.ts 注册到 Firebase 的组件注册表:
_registerComponent( new Component( 'database', (container, { instanceIdentifier: url }) => { const app = container.getProvider('app').getImmediate()!; const authProvider = container.getProvider('auth-internal'); const appCheckProvider = container.getProvider('app-check-internal'); return repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url); }, ComponentType.PUBLIC ).setMultipleInstances(true) );关键点:
- 组件名称为
'database',ComponentType.PUBLIC表示面向开发者公开; setMultipleInstances(true)允许同一 App 下存在多个Database实例(这正是getDatabase(app, url)中不同 URL 对应不同实例的机制来源);- 工厂函数从容器中取出
app、auth-internal、app-check-internal三个 Provider 后交给repoManagerDatabaseFromApp,因此Database实例天然集成了 Firebase Authentication 与 App Check 的 token 提供能力。
模块入口 packages/database/src/index.ts 在导入公共 API 后立即执行registerDatabase(),完成自注册。公共 API 的完整导出清单见 packages/database/src/api.standalone.ts。
围绕 Database 实例的配套函数
Database实例是下面这些顶层函数的直接操作对象,它们共同构成对连接生命周期与传输方式的管理能力。
goOffline() 与 goOnline():主动控制连接
export declare function goOffline(db: Database): void; export declare function goOnline(db: Database): void;goOffline(db):断开与服务器的连接。客户端默认会维持一条持久的自动重连连接;在离线期间,本地写入操作仍会立即触发事件,并被排队等待重连后自动重试(源码见 packages/database/src/api/Database.ts#L423-L427)。goOnline(db):重新连接并同步离线期间累积的状态,使客户端自动“追赶”上服务器(packages/database/src/api/Database.ts#L440-L444)。
两者内部最终分别调用repoInterrupt()/repoResume()(packages/database/src/core/Repo.ts#L836-L846),作用于PersistentConnection的 interrupt/resume。
connectDatabaseEmulator():接入模拟器
export declare function connectDatabaseEmulator( db: Database, host: string, port: number, options?: { mockUserToken?: EmulatorMockTokenOptions | string; } ): void;注意:该方法必须在任何其他数据库操作之前调用。源码中(packages/database/src/api/Database.ts#L350-L400)会检查db._instanceStarted:若实例已启动且参数不同,会直接抛出致命错误 “connectDatabaseEmulator() cannot initialize or alter the emulator configuration after the database instance has started.”。options.mockUserToken可用于在单元测试 Security Rules 时伪造认证 token(对应 util 文档 中的EmulatorMockTokenOptions)。
forceWebSockets() / forceLongPolling():传输层选择
export declare function forceWebSockets(): void; export declare function forceLongPolling(): void;forceWebSockets():强制使用 WebSocket 传输,内部调用BrowserPollConnection.forceDisallow();forceLongPolling():强制使用 long-polling 传输(若 databaseURL 中已使用 websocket 协议则会被忽略),内部调用WebSocketConnection.forceDisallow()与BrowserPollConnection.forceAllow()。
两个函数都会先经checkTransportInit()检查:如果传输层已初始化会发出警告。实现位于 packages/database/src/api/Database.ts#L287-L310。
enableLogging():调试日志开关
export declare function enableLogging(enabled: boolean, persistent?: boolean): any; export declare function enableLogging(logger: (message: string) => unknown): any;两种重载分别支持“布尔开关”(persistent为true时刷新页面后仍记住日志状态)与“自定义 logger 函数”,最终委托给enableLoggingImpl()(packages/database/src/api/Database.ts#L453-L467)。
与 Database 相关的其他核心类型
在 database.md 的模块总览中,Database类与以下类型共同构成@firebase/database的公共 API 面,了解它们有助于整体把握Database的使用场景:
- DatabaseReference:表示数据库中某个具体位置,用于读写数据,通过
ref(db, path)或child(parent, path)获得; - Query / QueryConstraint:对数据进行排序与过滤,由
orderByChild()、limitToFirst()、startAt()/endAt()/equalTo()等约束组合生成; - DataSnapshot:读取数据时收到的不可变数据快照,通过
val()提取内容; - OnDisconnect:客户端断线时自动写入/清除数据,常用于在线状态(presence)检测;
- ThenableReference:
push()返回的“既是 Promise 又是 Reference”的特殊类型,可立即当引用使用,也可 await 等待写入完成; - runTransaction():原子化地修改数据,配合
TransactionOptions/TransactionResult使用。
其中push()生成的唯一键按当前时间排序且包含 72 位随机熵,用于向列表追加数据时既按时间有序又难以被猜测(实现见 packages/database/src/core/util/NextPushId.ts 对应的nextPushId)。
小结与源码阅读指引
Database类是@firebase/database模块中连接“Firebase App 组件体系”与“Realtime Database 底层同步引擎”的枢纽:开发者通过getDatabase()获得它,通过它的app属性访问关联 App、通过type属性识别实例身份,而它内部持有的_repoInternal(Repo)则承担了全部网络通信与数据同步职责。掌握该类的实例化路径、懒启动机制与生命周期管理,是正确使用 Firebase Realtime Database 模块化 SDK 的前提。
如需深入源码,推荐按以下顺序阅读:
- 类定义与实例工厂:packages/database/src/api/Database.ts
- 组件注册:packages/database/src/register.ts
- 底层同步引擎:packages/database/src/core/Repo.ts
- 连接信息解析:packages/database/src/core/RepoInfo.ts
- 模块入口与导出:packages/database/src/index.ts、packages/database/src/api.standalone.ts
- 官方 API 参考:docs-devsite/database.database.md(本主题文档)、docs-devsite/database.md(模块总览)
- 开发工具
- 后端
【免费下载链接】firebase-js-sdk
Firebase Javascript SDK
相关推荐
Free-courses-with-Certificates:终极免费证书课程平台完全指南
Free courses with Certificates:终极免费证书课程平台完全指南 在竞争激烈的就业市场中,提升技能和丰富简历变得至关重要。Free c
adk-samples 零售虚拟试穿(Virtual Try-On)Agent 的 design-spec.md 配置全解:从 GCP 参数到双模式部署
adk samples 零售虚拟试穿(Virtual Try On)Agent 的 design spec.md 配置全解:从 GCP 参数到双模式部署 导读
示例工程PPTAgent 能做什么?一张图看懂 AI PPT 生成的 5大核心能力
PPTAgent 能做什么?一张图看懂 AI PPT 生成的 5大核心能力 PPTAgent(现已升级为 DeepPresenter)是一个开源的 AI PPT
AI AgentAI 应用后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考