ReactJS+PocketBase PWA实时连接无法恢复问题求助
问题
我用PocketBase做后端,前端通过Server-Sent Events(SSE)订阅后端实时更新。浏览器里一切正常,但把应用以PWA形式安装到手机后,最小化再重新打开会丢失SSE连接,页面/数据也不重新加载,连包含router.refresh()的初始useEffect块都没触发。
useVisibility.tsx
"use client"; import { useState, useEffect } from "react"; export function useVisibility() { const [isVisible, setVisible] = useState(true); useEffect(() => { const handleVisibilityChange = () => { setVisible(!document.hidden); }; document.addEventListener("visibilitychange", handleVisibilityChange); return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, []); return isVisible; }
AgendaLive.tsx
"use client"; import { Reservation } from "@/types"; import { useEffect } from "react"; import PocketBase from "pocketbase"; import { reservations } from "../tables"; import { useRouter } from "next/navigation"; import { useVisibility } from "@/hooks/useVisibility"; type Props = { token: string; date: string; }; const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL); export function AgendaLive({ token, date }: Props) { const router = useRouter(); const isVisible = useVisibility(); useEffect(() => { if (!isVisible) return; router.refresh(); }, [isVisible, router]); useEffect(() => { if (!isVisible) return; pb.authStore.save(token); pb.collection(reservations).subscribe<Reservation>("*", (event) => { if (event.record.date !== date) { return; } router.refresh(); }); return () => { pb.collection(reservations).unsubscribe("*"); }; }, [token, date, router, isVisible]); return null; }
解决方案
1. 修复可见性状态的初始值与更新逻辑
当前useVisibility初始状态固定为true,但PWA从后台恢复时,document.hidden可能已经是false,不会触发状态变更,导致依赖isVisible的useEffect不执行。修改后让初始状态直接匹配当前页面可见性:
"use client"; import { useState, useEffect } from "react"; export function useVisibility() { // 初始状态直接取当前页面可见性 const [isVisible, setVisible] = useState(!document.hidden); useEffect(() => { const handleVisibilityChange = () => { setVisible(!document.hidden); }; document.addEventListener("visibilitychange", handleVisibilityChange); return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, []); return isVisible; }
2. 补充监听窗口焦点事件
部分场景下PWA恢复时visibilitychange事件触发延迟或不触发,新增window.focus监听确保页面激活时能触发更新:
"use client"; import { useState, useEffect } from "react"; export function useVisibility() { const [isVisible, setVisible] = useState(!document.hidden); useEffect(() => { const handleVisibilityChange = () => { setVisible(!document.hidden); }; const handleFocus = () => { // 页面获得焦点时强制更新可见状态 setVisible(true); }; document.addEventListener("visibilitychange", handleVisibilityChange); window.addEventListener("focus", handleFocus); return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); window.removeEventListener("focus", handleFocus); }; }, []); return isVisible; }
3. 页面恢复可见时重新建立SSE订阅
PWA后台运行时SSE连接可能被浏览器主动断开,需要在页面恢复可见时重新订阅,并先清理旧连接:
"use client"; import { Reservation } from "@/types"; import { useEffect } from "react"; import PocketBase from "pocketbase"; import { reservations } from "../tables"; import { useRouter } from "next/navigation"; import { useVisibility } from "@/hooks/useVisibility"; type Props = { token: string; date: string; }; const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL); export function AgendaLive({ token, date }: Props) { const router = useRouter(); const isVisible = useVisibility(); useEffect(() => { if (!isVisible) return; // 页面恢复可见时强制刷新数据 router.refresh(); }, [isVisible, router]); useEffect(() => { let unsubscribe: (() => void) | undefined; const setupSubscription = () => { pb.authStore.save(token); unsubscribe = pb.collection(reservations).subscribe<Reservation>("*", (event) => { if (event.record.date !== date) { return; } router.refresh(); }); }; if (isVisible) { setupSubscription(); } // 监听可见性变化,重新订阅 const handleVisibilityChange = () => { if (!document.hidden) { // 先取消旧订阅(如果存在) unsubscribe?.(); setupSubscription(); router.refresh(); } }; document.addEventListener("visibilitychange", handleVisibilityChange); return () => { unsubscribe?.(); document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, [token, date, router, isVisible]); return null; }
4. 优化PWA的Service Worker缓存策略(可选)
如果用Next.js的PWA插件,确保SSE请求不被缓存拦截,在next.config.js中添加配置:
/** @type {import('next').NextConfig} */ const nextConfig = { // ...其他配置 pwa: { dest: "public", runtimeCaching: [ { urlPattern: /^https:\/\/your-pocketbase-url\/api\/realtime.*/, handler: "NetworkFirst", // 优先使用网络请求保证实时性 options: { cacheName: "sse-realtime", expiration: { maxEntries: 1, maxAgeSeconds: 60, }, }, }, ], }, }; module.exports = nextConfig;
内容的提问来源于stack exchange,提问作者Tobias Benkner
相关产品推荐
相关产品推荐

