You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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}>

存在两个核心问题:

  1. 必须通过myStore.value.bookName访问属性,无法直接使用myStore.bookName
  2. 同步数据库的$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}>

方案说明

  1. 直接访问属性:通过Proxy拦截get操作,直接返回内部state的属性值,用户可以像Svelte 4一样使用myStore.bookName。
  2. 自动同步到数据库:Proxy的set操作会更新内部响应式state,并调用syncToFirebase将数据同步到Firebase,无需在组件中编写$effect。
  3. 数据库变化同步:setPath方法订阅Firebase路径,当数据库数据变化时更新内部state,Svelte的响应式系统会自动更新UI。

内容的提问来源于stack exchange,提问作者Nikolay Dyankov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 16:08:13