Svelte使用Firestore Query Snapshot遇解构错误的技术问询
问题分析与解决方案
问题背景
开发基于Svelte的Firebase数据库应用,希望将普通Firebase调用改为Query Snapshot实现数据库变更时页面实时更新。原代码可正常获取用户列表但无实时更新能力,修改为Query Snapshot代码后,控制台能实时输出用户数据,但页面无法渲染,且allUsersStore.js的try/catch中出现错误:Cannot destructure property 'users' of '(intermediate value)' as it is undefined。
正常工作代码
allUsersStore.js
import { fetchUsers } from "@api/allUsers"; import { onMount } from "svelte"; import { writable } from "svelte/store" export function allUsersStore() { const users = writable([]); const loading = writable(false); onMount(loadUsers); async function loadUsers() { loading.set(true); try { const {users: theUsers} = await fetchUsers(); users.set(theUsers); } catch(e) { console.log("ERROR!" + e.message); } finally { loading.set(false); } } return { users: { subscribe: users.subscribe }, loading: { subscribe: loading.subscribe } } }
allUsers.js
import { db } from "@db/index"; import { collection, getDocs, query, orderBy } from "firebase/firestore"; async function fetchUsers() { const q = query(collection(db, "userData"), orderBy('fullName', 'asc')); const qSnapshot = await getDocs(q); const users = qSnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id })); return {users}; }
+page.svelte
<script> import { allUsersStore } from "@stores/allUsersStore"; const { users, loading} = allUsersStore(); </script> {#each $users as user (user.id)} <h2>{user.fullName}</h2> {/each}
修改后的非工作代码(仅修改allUsers.js)
async function fetchUsers() { const q = query(collection(db, "userData"), orderBy('fullName', 'asc')); const unsubscribe = onSnapshot(q, (querySnapshot) => { const users = []; querySnapshot.forEach((doc) => { users.push(doc.data()); }); for (const x of $users) { console.log(x.fullName) } return {users}; }); }
问题原因
- 函数无返回值:修改后的
fetchUsers是async函数,但内部使用onSnapshot(实时监听API,非Promise),函数本身未返回任何值,导致await fetchUsers()得到undefined,解构{users}时触发错误。 - 回调返回无效:
onSnapshot回调中的return {users}仅在回调内部生效,无法传递到外部的fetchUsers函数。 - 错误使用Store语法:回调中使用
$users是Svelte Store的订阅语法,仅能在组件或Store的订阅回调中使用,此处直接调用会报错。 - 未处理订阅清理:未保存并在组件销毁时调用
unsubscribe,可能导致内存泄漏。
最佳实践方案
将实时监听逻辑与Svelte Store结合,由Store管理状态生命周期,确保实时数据能同步更新到页面。
步骤1:重构API层,提供订阅函数
修改allUsers.js,返回一个用于监听实时数据的订阅函数:
import { db } from "@db/index"; import { collection, query, orderBy, onSnapshot } from "firebase/firestore"; export function subscribeToUsers(callback) { const q = query(collection(db, "userData"), orderBy('fullName', 'asc')); // 返回unsubscribe函数,用于后续清理 return onSnapshot(q, (querySnapshot) => { const users = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id })); // 通过回调传递实时更新的用户数据 callback(users); }); }
步骤2:修改Store,处理实时订阅与状态更新
修改allUsersStore.js,在组件挂载时启动监听,并处理订阅清理:
import { subscribeToUsers } from "@api/allUsers"; import { onMount } from "svelte"; import { writable } from "svelte/store" export function allUsersStore() { const users = writable([]); const loading = writable(true); let unsubscribe; onMount(() => { loading.set(true); // 启动实时监听,更新Store数据 unsubscribe = subscribeToUsers((newUsers) => { users.set(newUsers); loading.set(false); }); // 组件销毁时取消订阅,防止内存泄漏 return () => { if (unsubscribe) unsubscribe(); }; }); return { users: { subscribe: users.subscribe }, loading: { subscribe: loading.subscribe } } }
步骤3:页面组件无需修改
原+page.svelte代码保持不变,通过订阅Store自动获取实时更新的数据。
方案优势
- 状态同步:实时数据变更会直接更新Store,页面通过自动订阅Store实现实时渲染。
- 生命周期管理:在组件挂载时启动监听,销毁时取消订阅,避免内存泄漏。
- 职责清晰:API层负责数据获取逻辑,Store负责状态管理,符合模块化开发规范。
内容的提问来源于stack exchange,提问作者Yiags1978
相关产品推荐
相关产品推荐

