• 简体中文
  • 用 URL 连接状态

    用 URL hash 连接状态

    如果你想把 store 的状态连接到 URL hash,可以自己创建一个 hash storage。

    import { create } from 'zustand'
    import { persist, StateStorage, createJSONStorage } from 'zustand/middleware'
    
    const hashStorage: StateStorage = {
      getItem: (key): string => {
        const searchParams = new URLSearchParams(location.hash.slice(1))
        const storedValue = searchParams.get(key) ?? ''
        return JSON.parse(storedValue)
      },
      setItem: (key, newValue): void => {
        const searchParams = new URLSearchParams(location.hash.slice(1))
        searchParams.set(key, JSON.stringify(newValue))
        location.hash = searchParams.toString()
      },
      removeItem: (key): void => {
        const searchParams = new URLSearchParams(location.hash.slice(1))
        searchParams.delete(key)
        location.hash = searchParams.toString()
      },
    }
    
    export const useBoundStore = create()(
      persist(
        (set, get) => ({
          fishes: 0,
          addAFish: () => set({ fishes: get().fishes + 1 }),
        }),
        {
          name: 'food-storage', // unique name
          storage: createJSONStorage(() => hashStorage),
        },
      ),
    )

    持久化并通过 URL 参数连接状态(示例:URL 查询参数)

    有时你希望有条件地把状态连接到 URL。下面的示例展示了 URL 查询参数的用法,同时让状态与另一种持久化实现(例如 localstorage)保持同步。

    如果你希望 URL 参数始终被填充,可以去掉 getUrlSearch() 上的条件判断。

    下面的实现会在相关状态发生变化时,就地更新 URL,而无需刷新页面。

    import { create } from 'zustand'
    import { persist, StateStorage, createJSONStorage } from 'zustand/middleware'
    
    const getUrlSearch = () => {
      return window.location.search.slice(1)
    }
    
    const persistentStorage: StateStorage = {
      getItem: (key): string => {
        // Check URL first
        if (getUrlSearch()) {
          const searchParams = new URLSearchParams(getUrlSearch())
          const storedValue = searchParams.get(key)
          return JSON.parse(storedValue as string)
        } else {
          // Otherwise, we should load from localstorage or alternative storage
          return JSON.parse(localStorage.getItem(key) as string)
        }
      },
      setItem: (key, newValue): void => {
        // Check if query params exist at all, can remove check if always want to set URL
        if (getUrlSearch()) {
          const searchParams = new URLSearchParams(getUrlSearch())
          searchParams.set(key, JSON.stringify(newValue))
          window.history.replaceState(null, '', `?${searchParams.toString()}`)
        }
    
        localStorage.setItem(key, JSON.stringify(newValue))
      },
      removeItem: (key): void => {
        const searchParams = new URLSearchParams(getUrlSearch())
        searchParams.delete(key)
        window.location.search = searchParams.toString()
      },
    }
    
    type LocalAndUrlStore = {
      typesOfFish: string[]
      addTypeOfFish: (fishType: string) => void
      numberOfBears: number
      setNumberOfBears: (newNumber: number) => void
    }
    
    const storageOptions = {
      name: 'fishAndBearsStore',
      storage: createJSONStorage<LocalAndUrlStore>(() => persistentStorage),
    }
    
    const useLocalAndUrlStore = create<LocalAndUrlStore>()(
      persist<LocalAndUrlStore>(
        (set) => ({
          typesOfFish: [],
          addTypeOfFish: (fishType) =>
            set((state) => ({ typesOfFish: [...state.typesOfFish, fishType] })),
    
          numberOfBears: 0,
          setNumberOfBears: (numberOfBears) => set(() => ({ numberOfBears })),
        }),
        storageOptions,
      ),
    )
    
    export default useLocalAndUrlStore

    在组件中生成 URL 时,可以调用 buildShareableUrl:

    const buildURLSuffix = (params, version = 0) => {
      const searchParams = new URLSearchParams()
    
      const zustandStoreParams = {
        state: {
          typesOfFish: params.typesOfFish,
          numberOfBears: params.numberOfBears,
        },
        version: version, // version is here because that is included with how Zustand sets the state
      }
    
      // The URL param key should match the name of the store, as specified as in storageOptions above
      searchParams.set('fishAndBearsStore', JSON.stringify(zustandStoreParams))
      return searchParams.toString()
    }
    
    export const buildShareableUrl = (params, version) => {
      return `${window.location.origin}?${buildURLSuffix(params, version)}`
    }

    生成的 URL 大致如下(为了便于阅读,这里未做任何编码):

    https://localhost/search?fishAndBearsStore={"state":{"typesOfFish":["tilapia","salmon"],"numberOfBears":15},"version":0}}

    演示