如何在Svelte 5中实现与Firebase实时数据库双向绑定的Store?
在Svelte 5中实现与Firebase实时数据库双向绑定的Store
需求概述
需要创建一个可与Firebase实时数据库路径双向绑定的Store,实现以下效果:
- 数据库值变化时自动更新UI
- 用户修改UI输入值时自动同步到数据库
- 可直接通过
myStore.bookName这类方式访问数据库对象的属性(无需.value)
Svelte 4实现参考
// $lib/firebase.ts export function writableRealtimeStore<T>() { let unsubscribe: () => void = () => {} let objectRef: any const store = writable<T | null>(null) let storeSet = store.set return { subscribe: store.subscribe, set: (value: any) => { return set(objectRef, value) }, update: () => {}, setPath: (path: string) => { objectRef = ref(realtimeDB, path) unsubscribe() unsubscribe = onValue(objectRef, (snapshot) => { storeSet((snapshot.val() as T) ?? null) }) }, } } // $lib/stores.ts export const myStore = writableRealtimeStore()
<!-- routes/+page.svelte --> <script lang="ts"> import { myStore } from '$lib/stores' myStore.setPath('/books/<book_id>') </script> <input type="text" bind:value={myStore.bookName}>
Svelte 5当前实现的问题
当前尝试的代码无法复刻Svelte 4的简洁性:
// $lib/firebase.ts export function createRealtimeStore<T>() { let unsubscribe = () => {} let store: { value: T | undefined } = $state({ value: undefined }) let _ref: DatabaseReference return { get value(): T | undefined { return store.value }, update: () => { if (_ref) set(_ref, store.value) }, setPath: (path: string) => { _ref = ref(realtime, path) unsubscribe() unsubscribe = onValue(_ref, (snapshot) => { store.value = snapshot.val() }) }, unsubscribe, } } // $lib/stores.ts export let myStore = createRealtimeStore()
<!-- routes/+page.svelte --> <script lang="ts"> import { myStore } from '$lib/stores' myStore.setPath('/books/<book_id>') $effect(() => { if (myStore.value) { myStore.update() } }) </script> <input type="text" bind:value={myStore.value.bookName}>
存在两个核心问题:
- 必须通过
myStore.value.bookName访问属性,无法直接使用myStore.bookName - 同步数据库的
$effect逻辑必须写在组件内,无法封装到Store中
解决方案
实现思路
利用Svelte 5的$state响应式状态结合JavaScript Proxy,拦截属性的读写操作,实现:
- 直接访问Store属性(无需
.value) - 属性修改时自动同步到Firebase
- 数据库变化时自动更新Store状态
完整代码实现
// $lib/firebase.ts import { ref, onValue, set, DatabaseReference } from 'firebase/database'; import { realtime } from './firebase-config'; // 替换为你的Firebase配置导出 export function createRealtimeStore<T extends object>() { // 内部响应式状态,存储数据库数据 let state = $state<T | undefined>(undefined); let _ref: DatabaseReference | undefined; let unsubscribe = () => {}; // 同步状态到Firebase的函数 const syncToFirebase = () => { if (_ref && state) { set(_ref, state); } }; // 创建Proxy,拦截属性读写 const storeProxy = new Proxy({} as T & { setPath: (path: string) => void; unsubscribe: () => void }, { get(target, prop) { // 处理setPath和unsubscribe方法 if (prop === 'setPath') { return (path: string) => { _ref = ref(realtime, path); unsubscribe(); // 订阅数据库变化,更新内部状态 unsubscribe = onValue(_ref, (snapshot) => { state = snapshot.val() as T; }); }; } if (prop === 'unsubscribe') { return unsubscribe; } // 访问属性时直接返回state中的对应值 return state?.[prop as keyof T]; }, set(target, prop, value) { // 修改属性时更新内部状态,并同步到Firebase if (state && prop !== 'setPath' && prop !== 'unsubscribe') { state = { ...state, [prop as keyof T]: value }; syncToFirebase(); return true; } return false; } }); return storeProxy; }
// $lib/stores.ts export const myStore = createRealtimeStore<{ bookName: string; author: string }>();
<!-- routes/+page.svelte --> <script lang="ts"> import { myStore } from '$lib/stores'; myStore.setPath('/books/<book_id>'); </script> <input type="text" bind:value={myStore.bookName}> <input type="text" bind:value={myStore.author}>
方案说明
- 直接访问属性:通过Proxy拦截
get操作,直接返回内部state的属性值,用户可以像Svelte 4一样使用myStore.bookName。 - 自动同步到数据库:Proxy的
set操作会更新内部响应式state,并调用syncToFirebase将数据同步到Firebase,无需在组件中编写$effect。 - 数据库变化同步:
setPath方法订阅Firebase路径,当数据库数据变化时更新内部state,Svelte的响应式系统会自动更新UI。
内容的提问来源于stack exchange,提问作者Nikolay Dyankov
相关产品推荐
相关产品推荐

