这就是我在 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}
此存储是双向响应的 - 当数据库中的值发生变化时,它会更新 UI,而当用户更新输入的值时,数据库也会发生变化。我可以直接访问数据库对象的属性
myStore.bookName
。
但是使用 Svelte 5 我无法获得商店对象的相同行为:
// $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 (project.value) {
project.update()
}
})
</script>
<input type="text" bind:value={myStore.value.bookName}
两个问题:
myStore.value.bookName
,而不是清洁工myStore.bookName
。$effect
符文必须位于页面中,而不是在创建商店的函数中,因为$effect
只能在组件初始化期间调用,否则会出现错误。总的来说,Svelte 4 的做法更加干净,使用起来也很愉快,我不相信你不能用新的、据称经过改进的商店系统做同样的事情。
您仍然可以使用
myStore.bookName
,尽管这需要在状态对象上传播数据。要判断数据是否已实际加载,您可能需要一个单独的标志。
Object.assign(store, snapshot.val());
在这种情况下,还会从状态对象中单独返回各种函数,这样就不会发生冲突。这是否比使用 value
包装更好还有待商榷。