代码之家  ›  专栏  ›  技术社区  ›  roeland

从减速机挂钩保存到本地存储

  •  1
  • roeland  · 技术社区  · 6 年前

    我正在使用useReducer钩子保存一些全局状态。因为我想在关闭浏览器时保存一些设置,所以将这些设置保存到本地存储。

    目前,我使用dispatch保存设置,并使用一个单独的函数将其保存到本地存储,但如果在dispatch之后自动保存设置,那就更好了。(有时我忘记保存到本地存储,并且在状态/本地存储之间有差异)

    我想答案是不要这样做,但有什么选择呢?(不使用redux)

    2 回复  |  直到 6 年前
        1
  •  3
  •   zkcro    6 年前

    编辑 :灵感来源于 locally persisting a useState hook ,这是本地持久化 useReducer

    import { useReducer, useEffect } from 'react'
    
    function useLocallyPersistedReducer(reducer, defaultState, storageKey, init = null) {
      const hookVars = useReducer(reducer, defaultState, (defaultState) => {
        const persisted = JSON.parse(localStorage.getItem(storageKey))
        return persisted !== null
          ? persisted
          : init !== null ? init(defaultState) : defaultState
      })
    
      useEffect(() => {
        localStorage.setItem(storageKey, JSON.stringify(hookVars[0]))
      }, [storageKey, hookVars[0]])
    
      return hookVars
    }
    

    这样就不需要额外的ref,并增加了对 optional init function


    // DO NOT USE THIS; see the updated implementation above instead
    import { useRef, useReducer } from 'react'
    
    function useLocallyPersistedReducer(reducer, defaultState, storageKey) {
      const isLoading = useRef(true)
      if (isLoading.current) {
        try {
          const persisted = JSON.parse(localStorage.getItem(storageKey))
          defaultState = persisted
        } catch (e) {
          console.warn(`Failed to load state '${storageKey}' from localStorage; using default state instead`)
        }
        isLoading.current = false
      }
    
      const [ state, dispatch ] = useReducer(reducer, defaultState)
    
      try {
        localStorage.setItem(storageKey, JSON.stringify(state))
      } catch (e) {
        console.warn(`Failed to store updated state '${storageKey}'`)
      }
    
      return [ state, dispatch ]
    }
    

    作为一个粗糙的工作,它是有效的,但可能有一些细微的差别,我错过了。希望这能给你一个想法,你可以如何处理它!

        2
  •  1
  •   Eduard Keilholz    6 年前

    我认为让一个服务负责将数据从本地存储区存储到本地存储区是明智的。所以您可能想要创建一个带有get和set函数的服务。您可以从redux中的效果调用服务。

    在我看来,减速机是用来改变状态的。

    顺便说一句,根据您的需要,我不明白为什么您不应该在本地存储中存储数据,只要(使用redux时)您记住redux状态是您唯一的真理。在我看来,将数据存储在某个地方以在初始化时恢复该状态是一种很好的方法。

    下面是使用ngrx的示例:

    @Effect()
    myAction$: Observable<Action> = this.actions$.pipe(
      ofType<MyAction>(actions.myAction),
      debounceTime(300),
      switchMap((action) =>
        this.service.post(action.dto).pipe(
          map((response: SomeDto) => {
            // This updates the state using a reducer
            return new CreateShortenedLinkSuccess(response);
          }),
          tap((value) => {
            // Here, you can use the value param, to save
            // changes to localStorage
            this.router.navigate([`/mgmt/link/${value.dto.id}`]);
          }),
          catchError((response: HttpErrorResponse) => {
            // Do scary stuff
          })
        )
      )
    );
    
        3
  •  0
  •   GNUton    5 年前

    import { useReducer, useEffect, Dispatch } from 'react'
    
    // Credits: https://stackoverflow.com/questions/54346866/save-to-localstorage-from-reducer-hook
    export type ReducerType<T> = (state: T, action: ActionType) => (T)
    export type InitType<T> = (state: T) => void
    export interface ActionType {
        type: string
    }
    
    var dispatcher:Dispatch<ActionType> | null = null
    
    const LOCAL_STORAGE_UPDATED_IN_A_DIFFERENT_WINDOW = "LOCAL_STORAGE_UPDATED_IN_A_DIFFERENT_WINDOW"
    export function useLocallyPersistedReducer<T>
        (reducer: ReducerType<T>, defaultState: T, storageKey: string, init?:InitType<T>) : [T, Dispatch<ActionType>] {
    
        const reducerWrapper = (status: T, action: ActionType) => {
            switch (action.type){
                case LOCAL_STORAGE_UPDATED_IN_A_DIFFERENT_WINDOW:
                    const statusReadFromLocalStorage = JSON.parse(localStorage.getItem(storageKey) || "{}")
                    return statusReadFromLocalStorage
                default: return reducer(status, action)
            }
        }
    
        const [state, dispatch] = useReducer(reducerWrapper, defaultState, (defaultState) => {
            const persisted = JSON.parse(localStorage.getItem(storageKey) as string)
            return persisted !== null
                ? persisted
                : init ? init(defaultState) : defaultState
        })
    
        // NOTE Even if this codes runs multiple time we add only one event listener as long as the callback is singleton
        dispatcher = dispatch
        window.addEventListener('storage', listenForStorageChangesInOtherWindows, {once: true});
    
        useEffect(() => {
            localStorage.setItem(storageKey, JSON.stringify(state))
            // eslint-disable-next-line react-hooks/exhaustive-deps
        }, [localStorage, storageKey, state])
    
        return [state, dispatch]
    }
    
    const listenForStorageChangesInOtherWindows = (key: any) => {
        dispatcher && dispatcher({type: LOCAL_STORAGE_UPDATED_IN_A_DIFFERENT_WINDOW})
    }