戰(zhàn)指南:讓 Firebase 在 Zone 與 Zoneless Angular 應(yīng)用中穩(wěn)定運(yùn)行)
后端【免費(fèi)下載鏈接】angularfireAngular Firebase ??項(xiàng)目地址https://gitcode.com/gh_mirrors/an/angularfire點(diǎn)擊查看免費(fèi)下載AngularFire 通過對 Firebase JS SDK 與 RxFire 的 Zone 包裝確保 Firebase 的定時(shí)器、網(wǎng)絡(luò)回調(diào)等副作用不會(huì)污染 Angular 的變更檢測流程同時(shí)保證 SSR/SSG 場景下頁面渲染會(huì)等待數(shù)據(jù)就緒。本文基于 docs/zones.md 并對照倉庫源碼系統(tǒng)講解 Zone Wrappers 的工作原理、注入上下文的正確用法、日志級別的調(diào)整方式以及底層?zoneWrap的實(shí)現(xiàn)機(jī)制幫助你徹底消除Calling Firebase APIs outside of an Injection context警告并寫出對 Zone 和 Zoneless 均健壯的 AngularFire 代碼。什么是 Zone WrappersAngularFire 包裝了框架無關(guān)的 Firebase JS SDK 與 RxFire以同時(shí)在 Zone 應(yīng)用與 ZonelessZone.js 移除應(yīng)用中保證功能的正確性。這些包裝器的核心作用是把 Firebase API 的調(diào)用移出 Angular zone 之外執(zhí)行從而隔離諸如定時(shí)器timer之類的副作用避免它們持續(xù)觸發(fā)變更檢測、破壞應(yīng)用穩(wěn)定性。一個(gè)值得注意的設(shè)計(jì)是Observable、基于 Promise 的 API 以及帶回調(diào)的 API 會(huì)有意地讓應(yīng)用保持不穩(wěn)定狀態(tài)直到返回初始值。這正是服務(wù)器端渲染SSR與靜態(tài)站點(diǎn)生成SSG / 預(yù)渲染所需要的——Angular 的PendingTasks機(jī)制會(huì)讓頁面渲染一直等待直到數(shù)據(jù)到達(dá)后再序列化 HTML從而避免首屏空白或超時(shí)。不進(jìn)行 Zone 包裝的后果如果你繞過 AngularFire、直接使用 Firebase 或 RxFire 的 API或者 AngularFire API 在注入上下文injection context之外被調(diào)用就可能遇到不穩(wěn)定問題當(dāng)應(yīng)用不穩(wěn)定時(shí)變更檢測、雙向綁定和 rehydration水合可能無法按預(yù)期工作進(jìn)而引發(fā)或隱蔽或明顯的 BugSSR 與 SSG預(yù)渲染可能超時(shí)或渲染出空白頁面。不過也存在大量 Zone 包裝無關(guān)緊要的場景例如響應(yīng)用戶輸入而增刪改文檔、用戶登錄、調(diào)用 Cloud Function 等一次性操作。只要沒有啟動(dòng)長期存在的副作用應(yīng)用通常是安全的——大多數(shù)基于 Promise 的 API 不經(jīng)過 Zone 包裝也相當(dāng)安全真正需要警惕的是onSnapshot、collectionData這類會(huì)產(chǎn)生長生命周期訂閱或回調(diào)的 API。保持調(diào)用處于注入上下文之中觸發(fā)警告的最常見方式是在異步回調(diào)內(nèi)部調(diào)用 AngularFire API。例如在已登錄用戶的switchMap回調(diào)里構(gòu)建 Firestore 查詢// 危險(xiǎn)寫法回調(diào)執(zhí)行時(shí)外圍服務(wù)的注入上下文早已消失 readonly todos$ this.user$.pipe( switchMap((user) collectionData(collection(this.firestore, users/${user.uid}/todos)), ), );外圍服務(wù)是在注入上下文中創(chuàng)建的但回調(diào)是在之后、上下文已經(jīng)消失時(shí)才運(yùn)行的此時(shí) AngularFire 無法再包裝這次調(diào)用。解決辦法是在上下文仍然有效的時(shí)候任意字段初始化器或構(gòu)造函數(shù)中捕獲一個(gè)EnvironmentInjector然后在回調(diào)內(nèi)部通過 Angular 的runInInjectionContextAPI 重新建立上下文private readonly injector inject(EnvironmentInjector); readonly todos$ this.user$.pipe( switchMap((user) runInInjectionContext(this.injector, () collectionData(collection(this.firestore, users/${user.uid}/todos)), ), ), );在runInInjectionContext內(nèi)部AngularFire 可以重新包裝這次調(diào)用SSR/變更檢測的保證得以恢復(fù)警告也隨之消失。如果調(diào)用不依賴異步值更推薦的做法是把調(diào)用整體提升到回調(diào)之外只在注入上下文仍活躍時(shí)執(zhí)行一次之后復(fù)用// 在上下文活躍時(shí)構(gòu)建一次之后復(fù)用因此無需包裝 private readonly items$ collectionData(collection(this.firestore, items)); readonly refreshed$ this.refresh$.pipe(switchMap(() this.items$));只有在調(diào)用確實(shí)必須在回調(diào)內(nèi)部執(zhí)行例如上面的按用戶查詢因?yàn)樗枰训卿浻脩舻膗id時(shí)才需要使用runInInjectionContext。為什么注入上下文是必需的AngularFire無法創(chuàng)建注入上下文只能借用你當(dāng)前所在的上下文。當(dāng)你調(diào)用一個(gè)被包裝的 API 時(shí)AngularFire 做的第一件事就是用inject()向 Angular 索要三樣?xùn)|西見 src/zones.ts自身的調(diào)度器服務(wù)?AngularFireSchedulersAngular 的PendingTasks注冊表一個(gè)EnvironmentInjector。inject()只能在注入上下文中工作因此在上下文之外第一項(xiàng)就會(huì)拋錯(cuò)其余兩項(xiàng)永遠(yuǎn)不會(huì)被訪問到。AngularFire 會(huì)捕獲這個(gè)錯(cuò)誤在開發(fā)模式下按下文日志系統(tǒng)所述發(fā)出警告然后什么都不附加地直接調(diào)用 Firebase API。你與 AngularFire 的分工這個(gè)劃分值得清楚因?yàn)樗缍穗p方各自的職責(zé)你在調(diào)用點(diǎn)提供上下文——來自字段初始化器、構(gòu)造函數(shù)或顯式的runInInjectionContext。AngularFire在你交給調(diào)用本身的回調(diào)內(nèi)部重新進(jìn)入環(huán)境注入器——這就是為什么你從不需要自己包裝onSnapshot處理器。后者可以觸及應(yīng)用級別提供的任何東西但不包含在組件上聲明的 providers也不會(huì)延伸到所返回 Observable 的訂閱者——這正是上面switchMaprunInInjectionContext示例存在的意義。不同調(diào)用會(huì)失去什么失去什么取決于調(diào)用類型執(zhí)行異步工作的調(diào)用如onSnapshot、getDoc、collectionData通常會(huì)注冊到 Angular 的PendingTasks注冊表——SSR 會(huì)等待它完成后再序列化頁面在上下文之外調(diào)用時(shí)它永遠(yuǎn)不會(huì)被注冊頁面可能在數(shù)據(jù)到達(dá)前就完成序列化。立即返回的調(diào)用如getFirestore本來就不會(huì)被注冊因此失去的只是 Zone 處理。日志系統(tǒng)與調(diào)試開發(fā)過程中你可能會(huì)看到如下警告Calling Firebase APIs outside of an Injection context may destabilize your application leading to subtle change-detection and hydration bugs. Find more at https://github.com/angular/angularfire/blob/main/docs/zones.md不穩(wěn)定問題往往難以排查。為幫助調(diào)試AngularFire 在開發(fā)模式下無法完成 Zone 包裝時(shí)會(huì)發(fā)出警告——這些消息通??梢园踩雎缘?xiàng)目選擇寧可啰嗦也不遺漏。AngularFire 共提供三個(gè)日志級別對應(yīng)源碼 src/zones.ts 中的LogLevel枚舉SILENT 0、WARN 1、VERBOSE 2級別行為默認(rèn)場景Silent僅在 AngularFire API 于注入上下文之外被調(diào)用時(shí)顯示上述 banner 警告Zoneless 變更檢測下的默認(rèn)值Warn僅記錄阻塞性讀取、長時(shí)間任務(wù)以及高風(fēng)險(xiǎn)可能使應(yīng)用不穩(wěn)定的 APIZoneJS 下的默認(rèn)值Verbose記錄所有在注入上下文之外被調(diào)用的 AngularFire API幫助追蹤可能導(dǎo)致不穩(wěn)定的調(diào)用手動(dòng)開啟默認(rèn)級別的確定邏輯見 src/zones.tsisDevMode() typeof Zone ! undefined時(shí)為WARN否則為SILENT——這與文檔所述ZoneJS 默認(rèn) Warn、Zoneless 默認(rèn) Silent完全一致且注意非開發(fā)模式下默認(rèn)也是 Silent。你可以這樣修改日志級別import { setLogLevel, LogLevel } from angular/fire; setLogLevel(LogLevel.VERBOSE);setLogLevel與LogLevel均從包入口導(dǎo)出見 src/public_api.ts 的export * from ./zones。源碼視角?zoneWrap的包裝機(jī)制理解底層實(shí)現(xiàn)有助于把握整個(gè)體系。倉庫中每個(gè) Firebase 模塊的firebase.ts如 src/firestore/firebase.ts、src/analytics/firebase.ts、src/auth/firebase.ts都由tools/build.ts自動(dòng)生成統(tǒng)一把原生函數(shù)交給?zoneWrap包裝例如// Firestore異步/回調(diào)類 API 使用 blockUntilFirst true阻塞直至首個(gè)值 export const onSnapshot ?zoneWrap(_onSnapshot, true); export const getDoc ?zoneWrap(_getDoc, true); // 低風(fēng)險(xiǎn) API 顯式指定 VERBOSE 日志級別第三參數(shù) 2僅 Verbose 模式記錄 export const setDoc ?zoneWrap(_setDoc, true, 2); export const logEvent ?zoneWrap(_logEvent, false, 2);RxFire 的響應(yīng)式 API 同樣被包裝例如 src/firestore/rxfire.ts 中的collectionData ?zoneWrap(_collectionData, true)。?zoneWrap的核心邏輯src/zones.ts可歸納為三層嘗試進(jìn)入注入上下文依次inject調(diào)度器、PendingTasks、EnvironmentInjector失敗則按日志級別告警并退化為直接調(diào)用原生 API。包裝回調(diào)參數(shù)若blockUntilFirst為真對傳入的函數(shù)參數(shù)如onSnapshot的處理器先添加一個(gè)PendingTasks任務(wù)再用zoneWrapFn包裹——任務(wù)會(huì)在首個(gè)回調(diào)觸發(fā)后通過setTimeout(taskDone, 0)完成確保 SSR 等待首次數(shù)據(jù)。調(diào)度 Observable / Promise對返回的 Observable 使用subscribeOn(outsideAngular)observeOn(insideAngular)雙調(diào)度器由?AngularFireSchedulers在NgZone.runOutsideAngular/NgZone.run中分別創(chuàng)建使訂閱發(fā)生在 Zone 外、發(fā)射回 Zone 內(nèi)阻塞型 Observable 還會(huì)追加pendingUntilEventPromise 則用PendingTasks包裹并在解析/拒絕時(shí)通過runInInjectionContext回到 Zone 內(nèi)執(zhí)行。這套機(jī)制的最終效果是Firebase 的長連接、監(jiān)聽、定時(shí)器等副作用全部在 Zone 外運(yùn)行而每次數(shù)據(jù)發(fā)射都會(huì)正確地回到 Zone 內(nèi)觸發(fā)變更檢測——Zone 應(yīng)用與 Zoneless 應(yīng)用因此都能獲得穩(wěn)定的行為。小結(jié)Zone Wrappers 是 AngularFire 保證應(yīng)用穩(wěn)定性的基礎(chǔ)設(shè)施它讓 Firebase 的副作用遠(yuǎn)離 Angular zone同時(shí)借助PendingTasks讓 SSR/SSG 等待數(shù)據(jù)就緒。使用時(shí)的三條核心準(zhǔn)則盡量讓 AngularFire 調(diào)用發(fā)生在注入上下文中——字段初始化器或構(gòu)造函數(shù)是最佳位置異步回調(diào)內(nèi)必須調(diào)用時(shí)先捕獲EnvironmentInjector再用runInInjectionContext重建上下文遇到警告時(shí)先判斷調(diào)用是否屬于高風(fēng)險(xiǎn)阻塞讀取、長生命周期任務(wù)再用setLogLevel(LogLevel.VERBOSE)輔助定位問題調(diào)用。掌握這些機(jī)制后無論是 ZoneJS 傳統(tǒng)應(yīng)用還是 Zoneless 新架構(gòu)你都能寫出既不產(chǎn)生隱蔽變更檢測 Bug、又能正確支持 SSR 預(yù)渲染的 AngularFire 代碼。贊分享后端【免費(fèi)下載鏈接】angularfireAngular Firebase ??項(xiàng)目地址https://gitcode.com/gh_mirrors/an/angularfire點(diǎn)擊查看免費(fèi)下載相關(guān)推薦AngularFire 實(shí)戰(zhàn)在 Firebase Cloud Functions 上部署 Angular UniversalSSR應(yīng)用AngularFire 實(shí)戰(zhàn)在 Firebase Cloud Functions 上部署 Angular UniversalSSR應(yīng)用 Angular U后端深入理解 NgRx Platform 的 ngrx/component在 zone-full 與 zone-less 模式下構(gòu)建響應(yīng)式 Angular 模板深入理解 NgRx Platform 的 ngrx/component在 zone full 與 zone less 模式下構(gòu)建響應(yīng)式 Angular 模板前端狀態(tài)管理AngularFire 集成指南在 Angular 應(yīng)用中接入 Firebase Authentication 與 SSR 登錄態(tài)同步AngularFire 集成指南在 Angular 應(yīng)用中接入 Firebase Authentication 與 SSR 登錄態(tài)同步 本指南圍繞 Angul后端上一篇如何實(shí)現(xiàn)微信聊天記錄永久備份與智能分析WeChatMsg開源工具完整指南下一篇終極ESP32開發(fā)指南從零開始快速上手物聯(lián)網(wǎng)項(xiàng)目創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考