将Svelte存储持久化到本地存储

3

我有一个使用Svelte(使用Astro的SSR)的应用程序,我正在尝试将我的数据保存到localStorage中。

在我看来,关于这个问题的文档并不足够,因为我似乎经常遇到困难。

到目前为止,我定义了以下存储:

import { writable } from 'svelte/store'

function createCart() {
    const { subscribe, set, update } = writable([]);
    return {
        subscribe,
        update: (item) => update(value => [...value, item]),
        remove: (item) => update(n => {
                    if(n.length > 0) n.pop() // only remove last item
                    return n // update the store value
                }),
        empty: () => set([])
    };
}

export const cart = createCart()

为了保存到localStorage(而不破坏我的SSR),我已经实现了以下内容:
import { writable } from 'svelte/store'

function createCart() {

    const defaultCartValue = []
    const isClient = typeof window !== 'undefined'
    const initialValue = isClient ? window.localStorage.getItem('cart') : defaultCartValue;

    const { subscribe, set, update } = writable(
        isClient 
            ? window.localStorage.setItem('cart', initialValue) 
            : defaultCartValue
    );
    return {
        subscribe: subscribe(value => localStorage.setItem('cart', ([
            value,
            ...initialValue
        ]))),
        update: (value) => isClient 
                ? window.localStorage.setItem(
                        'cart', 
                        JSON.stringify([...value, item])
                  )
                : update(value => [...value, item]),
        // TODO: fix remove items from localStorage
        remove: (item => update(n => {
                    if(n.length > 0) n.pop()
                    return n
                })
        ),
        empty: () => isClient
            ? window.localStorage.setItem('cart', defaultCartValue)
            : set(defaultCartValue)
    };
}

export const cart = createCart()

我一直在遇到 Cannot read property 'unsubscribe' of undefined 错误,其后跟着 stop is not a function 错误。

有没有人能帮我理解为什么会出现这2个错误?

2个回答

3
你遇到的错误是:
无法读取未定义的属性“取消订阅”,接着是不是一个函数。
这是因为store的subscribe方法必须返回一个unsubscribe(也叫stop)函数。尽管文档没有强调,但它在该方法的签名中显示:
subscribe: (subscription: (value: any) => void) => (() => void)

但是你自己的代码并没有做到这一点,而且说实话,它可能并不真正达到你的意图:

        subscribe: subscribe(value => localStorage.setItem('cart', ([
            value,
            ...initialValue
        ]))),

您在创建存储时立即调用了原始的subscribe,而不是在注册新的订阅者时调用。由于原始的subscribe确实返回一个unsubscribe函数,因此尝试订阅时不会出现“不是函数”的错误。但是,您也不会得到订阅,unsubscribe函数也不会返回另一个函数,这就解释了您看到的错误。

您需要的是像这样的内容:

subscribe: subscriber => {
  // do something on every new subscriber

  // subscribing 
  const unsubscribe = originalSubscribe(value => {
    // do something every time the value change FOR EVERY SUBSCRIBER

    subscriber(value) // notify the subscriber of the new value
  }

  // custom unsubscribe function
  return () => {
    // do something each time a subscriber unsubscribes

    unsubcribe() // unsubscribe from the original store
  }
}

如果你按照前面代码段中的注释来做,你应该会意识到,包装 subscribe 可能不是你想要存储在 localStorage 中的内容,因为这种方式得到的“钩子”是 针对每个订阅者 的,而不是每个 store 只有一次。
达成你想要的方法之一是重写可写的 store 的 setupdate 方法,比如像 svelte-local-storage-store 这样的库所展示的那样: does
    const {subscribe, set} = store

    stores[key] = {
      set(value: T) {
        updateStorage(key, value)
        set(value)
      },
      update(updater: Updater<T>) {
        const value = updater(get(store))

        updateStorage(key, value)
        set(value)
      },
      subscribe
    }

另一种简单方便、模块化的方法是订阅您的 store。
// a guard because we only want this in the clients
if (!isSSR() && typeof localStorage !== 'undefined') {
  originalStore.subscribe(value => {
    localStorage.setItem('key', JSON.stringify(value))
  })
}

如果您不介意将您的商店“始终激活”,那么这仅适用于以下情况:例如,可能不希望对其进行请求的“丰富”商店在获得其第一个订阅者时。


利用原始商店的 subscribe 以及避免急切地订阅它的陷阱的另一种方法是,在可读取商店中封装上述逻辑。

const createPersistedStore = initialValue => {
    const internal = writable(initialValue)

    const persisting = readable(initialValue, set => {
        // called when the store becomes active (i.e. first subscriber arrives)

        const unsubscribe = internal.subscribe(value => {

            updateLocalStorage(value) // <== will see each change once

            set(value)
        })

        return () => {
            // called when the store becomes inactive (i.e. last subscriber leaves)
            unsubscribe()
        }
    })

    return {
        set: internal.set,
        update: internal.update,
        subscribe: persisting.subscribe,
    }
}

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接