对于目前正在使用命名空间型 Firebase Web API 的应用(从 compat
库开始,一直回溯到 8 或更低版本),应考虑按照本指南中的说明迁移到模块化 API。
本指南假定您熟悉命名空间型 API,并且能够利用模块打包器(例如 webpack 或 Rollup)升级和持续进行模块化应用开发。
强烈建议在开发环境中使用模块打包器。如果您不使用模块打包器,将无法发挥模块化 API 在缩减应用大小方面的主要优势。您需要使用 npm 或 yarn 来安装 SDK。
本指南中的升级步骤将基于使用 Authentication 和 Cloud Firestore SDK 的虚构 Web 应用。通过学习这些示例,您可以掌握升级所有受支持的 Firebase Web SDK 所需了解的概念和实际步骤。
命名空间型 (compat
) 库简介
Firebase Web SDK 可以使用两种类型的库:
- 模块化 - 一种新的 API 接口,用于实现摇树优化(移除未使用的代码),使您的 Web 应用尽可能精简快速。
- 命名空间型 (
compat
) - 一种熟悉的 API 接口,与旧版本的 SDK 完全兼容,让您无需更改任何 Firebase 代码即可立即升级。与命名空间型库相比,兼容型库在大小或性能方面的优势很少,甚至没有优势。
本指南假定您将利用兼容型库来推动升级。通过这些库,您可以一边继续使用命名空间型代码,一边逐步针对模块化 API 重构代码。这意味着您在升级过程中可以更轻松地编译和调试应用。
如果应用极少调用 Firebase Web SDK (例如,只对 Authentication API 进行简单调用),可以直接重构旧版命名空间型代码,而不需要使用兼容型库。如果要升级此类应用,您可以按照本指南中有关“模块化 API”的说明进行操作,而无需使用兼容型库。
升级流程简介
升级流程的每个步骤都限定了范围,这样您便可以完成应用源代码的修改,然后在不出现中断的情况下编译并运行源代码。总的来说,如需升级应用,您需要执行以下操作:
- 将模块化库和兼容型库添加到您的应用中。
- 将代码中的 import 语句更新为使用兼容型库。
- 将针对单个产品(例如 Authentication)的代码重构为模块化样式。
- 可选:现在移除用于 Authentication 的 Authentication 兼容型库和兼容型代码,以便先在 Authentication 方面实现应用大小的精简,然后再继续。
- 将针对每个产品(例如 Cloud Firestore、FCM 等)的函数重构为模块化样式,然后进行编译和测试,直到完成所有方面。
- 将初始化代码更新为模块化样式。
- 从应用中移除所有剩余的兼容型语句和兼容型代码。
获取最新版 SDK
首先,使用 npm 获取模块化库和兼容型库:
npm i firebase@10.13.1 # OR yarn add firebase@10.13.1
将导入项更新为兼容型
更新依赖项后,为了确保代码正常运行,请将 import 语句更改为使用每个语句的“兼容型”版本。例如:
更新前:8 或更低版本
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
更新后:兼容型
// compat packages are API compatible with namespaced code
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
import 'firebase/compat/firestore';
重构为模块化样式
命名空间型 API 基于(以句点串联的)命名空间和服务模式,而模块化方法则主要围绕函数来组织代码。在模块化 API 中,firebase/app
包和其他包不会返回包含包中所有方法的全面导出结果,而是会导出一个个的函数。
在模块化 API 中,服务将作为第一个参数传递,函数随后会使用服务的详细信息执行其余操作。我们通过两个重构 Authentication 和 Cloud Firestore API 调用的示例来了解具体的工作原理。
示例 1:重构 Authentication 函数
重构前:兼容型
兼容型代码与命名空间型代码完全相同,但导入项已更改。
import firebase from "firebase/compat/app";
import "firebase/compat/auth";
const auth = firebase.auth();
auth.onAuthStateChanged(user => {
// Check for user status
});
重构后:模块化
getAuth
函数将 firebaseApp
作为其第一个参数。
onAuthStateChanged
函数不会像在命名空间型 API 中那样需要从 auth
实例串联;因此,它是一个自由函数,将 auth
作为其第一个参数。
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth(firebaseApp);
onAuthStateChanged(auth, user => {
// Check for user status
});
更新对 Auth 方法 getRedirectResult
的处理方式
模块化 API 在 getRedirectResult
中实施了一项重大变更。如果未调用重定向操作,模块化 API 会返回 null
,而命名空间型 API 则不同,它会返回一个 UserCredential
(其中包含一个 null
用户)。
重构前:兼容型
const result = await auth.getRedirectResult()
if (result.user === null && result.credential === null) {
return null;
}
return result;
重构后:模块化
const result = await getRedirectResult(auth);
// Provider of the access token could be Facebook, Github, etc.
if (result === null || provider.credentialFromResult(result) === null) {
return null;
}
return result;
示例 2:重构 Cloud Firestore 函数
重构前:兼容型
import "firebase/compat/firestore"
const db = firebase.firestore();
db.collection("cities").where("capital", "==", true)
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
重构后:模块化
getFirestore
函数将 firebaseApp
作为其第一个参数,后者是从前面的示例中的 initializeApp
返回的。请注意,在模块化 API 中,代码构建查询的方式有很大不同;没有串联,并且 query
或 where
等方法现在作为自由函数提供。
import { getFirestore, collection, query, where, getDocs } from "firebase/firestore";
const db = getFirestore(firebaseApp);
const q = query(collection(db, "cities"), where("capital", "==", true));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
更新对 Firestore DocumentSnapshot.exists
的引用
模块化 API 实施了一项重大变更,即属性 firestore.DocumentSnapshot.exists
已更改为方法。其功能基本相同(测试文档是否存在),但您必须重构代码以使用较新的方法,如下所示:
重构前:兼容型
if (snapshot.exists) {
console.log("the document exists");
}
重构后:模块化
if (snapshot.exists()) {
console.log("the document exists");
}
示例 3:组合命名空间型和模块化代码样式
在升级过程中使用兼容型库让您可以一边继续使用命名空间型代码,一边逐步针对模块化 API 重构代码。这意味着,您在将 Authentication 或其他 Firebase SDK 代码重构为模块化样式时,可以保留 Cloud Firestore 的现有命名空间型代码,并且仍然能使用两种代码样式成功编译您的应用。产品(例如 Cloud Firestore)中的命名空间型和模块化 API 代码也是如此;只要您导入兼容型软件包,新旧代码样式就可以共存:
import firebase from 'firebase/compat/app';
import 'firebase/compat/firestore';
import { getDoc } from 'firebase/firestore'
const docRef = firebase.firestore().doc();
getDoc(docRef);
请注意,虽然您的应用将可正常编译,但只有从应用中完全移除兼容型语句和代码,才能获享模块化代码在应用大小方面的优势。
更新初始化代码
更新应用的初始化代码,以使用模块化语法。重构应用中的所有代码后,请务必更新初始化代码;这是因为 firebase.initializeApp()
会初始化兼容型和模块化 API 的全局状态,而模块化 initializeApp()
函数仅会初始化模块化 API 的状态。
重构前:兼容型
import firebase from "firebase/compat/app"
firebase.initializeApp({ /* config */ });
重构后:模块化
import { initializeApp } from "firebase/app"
const firebaseApp = initializeApp({ /* config */ });
移除兼容型代码
为了发挥模块化 API 在精简应用大小方面的优势,您最终应将所有调用转换为上述的模块化样式,并从代码中移除所有 import "firebase/compat/*
语句。完成后,代码中不应再包含对 firebase.*
全局命名空间的引用或对任何其他命名空间型 API 代码的引用。
从窗口中使用兼容型库
模块化 API 经过优化,可以与模块(而不是浏览器的 window
对象)配合使用。该库之前的版本允许使用 window.firebase
命名空间加载和管理 Firebase。建议您以后不要采用这种做法,因为这种方法不允许消除未使用的代码。不过,对于不希望立即开始模块升级的开发者,JavaScript SDK 的兼容型版本也支持 window
。
<script src="https://www.gstatic.com/firebasejs/10.13.1/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/10.13.1/firebase-firestore-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/10.13.1/firebase-auth-compat.js"></script>
<script>
const firebaseApp = firebase.initializeApp({ /* Firebase config */ });
const db = firebaseApp.firestore();
const auth = firebaseApp.auth();
</script>
兼容型库在后台使用模块化代码,并提供与命名空间型 API 相同的 API;这意味着您可以参阅命名空间型 API 参考文档和命名空间型代码段了解详情。此方法不建议长期使用,而是应作为着手点来升级到完全模块化的库。
模块化 SDK 的优势和限制
与早期版本相比,完全模块化的 SDK 具有以下优势:
- 模块化 SDK 可大幅缩减应用大小。 它采用新型的 JavaScript 模块格式,允许执行“摇树优化”做法,这样就只需导入应用需要的工件。与使用命名空间型 API 构建的类似应用相比,使用模块化 SDK 进行摇树优化可使大小(以 KB 为单位)减少 80%,具体取决于您的应用。
- 模块化 SDK 将继续受益于后续的功能开发,但命名空间型 API 无法受益。