React Hooks 的分析 - useLocalStorage 与 useSessionStorage (ahooks)

Published on
1211 Words
Authors

ahooks 实现分析

代码实现:useLocalStorageStateuseSessionStorageStatecreateUseStorageState

文档/Demo:useLocalStorageStateuseSessionStorageState

ahooks 中实现 useLocalStorageState 与 useSessionStorageState 的方式是通过同一个方法进行逻辑封装,即 createUseStorageState。

先看下面的实现代码,

const useLocalStorageState = createUseStorageState(() => (isBrowser ? localStorage : undefined))

export default useLocalStorageState
const useSessionStorageState = createUseStorageState(() => (isBrowser ? sessionStorage : undefined))

export default useSessionStorageState

由于需要支持服务端渲染,所以在使用 storage 时需要判断是否为浏览器环境,

  • 若是,则根据需求传入对应的 storage: window.localStoragewindow.sessionStorage
  • 否则,传入 undefined,内部则不支持调用缓存 API

createUseStorageState

所以,现在需要重点关注 createUseStorageState 这个工厂函数。 在传递一个函数并调用它之后,可以返回一个对应的 hook,其基本结构是:

// Storage 即为 Web Storage API 的接口
export function createUseStorageState(getStorage: () => Storage | undefined) {
  function useStorageState<T>(key: string, options: Options<T> = {}) {
    //
  }

  return useStorageState
}

useStorageState

接收缓存的 key 与一系列 options,其涉及的主要接口定义为:

export interface Options<T> {
  defaultValue?: T | (() => T)
  serializer?: (value: T) => string
  deserializer?: (value: string) => T
  onError?: (error: unknown) => void
}

export type SetState<S> = S | ((prevState?: S) => S)

useStorageState 的内部主体结构是:

  • 使用 updateState 方法处理缓存的更新与删除
  • 使用 storage 接收缓存类型对象
  • getStoredValue 获取 storage 的默认值,如果本地没有值,则返回默认值
  • 内部定义默认的 serializer 与 deserializer,使用 JSON.stringify/JSON.parse 实现
  • 支持在 key 变化时,自动更新状态

updateState

在 useStorageState 中最主要的方法就是 updateState,与其他的 ahooks 中 hook 实现一致,比如 useCookieState,也是通过是否传入参数同时处理更新与删除。

function useStorageState<T>(key: string, options: Options<T> = {}) {
  // 通过将 getStorage() 返回的结果赋值给 storage,即可调用 Storage 上的方法
  let storage: Storage | undefined

  // https://github.com/alibaba/hooks/issues/800
  try {
    storage = getStorage()
  } catch (err) {
    onError(err)
  }

  // other code ...

  // 返回缓存的初始值
  function getStoredValue() {
    // ...
  }

  const [state, setState] = useState(getStoredValue)

  const updateState = (value?: SetState<T>) => {
    const currentState = isFunction(value) ? value(state) : value
    setState(currentState)

    // 传入的值为 undefined 时,即表示删除该缓存
    if (isUndef(currentState)) {
      storage?.removeItem(key)
    } else {
      // 传入的值不为空,则表示设置或更新缓存
      try {
        storage?.setItem(key, serializer(currentState))
      } catch (e) {
        console.error(e)
      }
    }
  }

  // updateState 使用 useMemoizedFn 包一层是为了使导出函数的地址不变
  return [state, useMemoizedFn(updateState)] as const
}

初始化 storage

这个有个知识点,在禁用 cookie 时,localstorage/sessionStorage 时无法使用/访问的,此时需要捕获异常,于是有了 onError 这个回调参数,可以查看这个 issue.

利用某些开源库,如 localstorage-slim 可以支持在无法使用 storage 时回退到内存存储 (in-memory store)

// https://github.com/alibaba/hooks/issues/800
try {
  storage = getStorage()
} catch (err) {
  onError(err)
}

getStoredValue

getStoredValue 函数用于获取缓存的初始值,并且在缓存的 key 更新时,再次获取其对应的缓存值更新状态。

其中,缓存的初始值的获取方式是:

  • 先调用 API storage.getItem() 从缓存中取值
    • 有反序列化函数,则调用反序列化函数得到正确的结果
  • 缓存中无值时,则采用传入的默认值
    • options.defaultValue 是函数形式,则返回函数调用结果
    • 否则直接返回 options.defaultValue

所以缓存 state 的存储结果可能为传入的类型 Tundefined,其中隐式的表示了未传递 defaultValue 时,其值为 undefined。

function useStorageState<T>(key: string, options: Options<T> = {}) {
  // 获取缓存初始值
  function getStoredValue() {
    // 缓存中存在值,反序列化该值
    try {
      const raw = storage?.getItem(key)
      if (raw) {
        return deserializer(raw)
      }
    } catch (e) {
      onError(e)
    }
    // 否则,返回 options 给出的默认值 defaultValue 或 undefined —— 隐式的表示未传递 defaultValue 时,即为 undefined
    if (isFunction(options.defaultValue)) {
      return options.defaultValue()
    }
    return options.defaultValue
  }

  const [state, setState] = useState(getStoredValue)

  // other code ...
}

serializer 与 deserializer

另外,内部提供了序列化与反序列化的方法,采用的是 JSON.stringifyJSON.parse,在 options 中也支持传入自定义的序列化与反序列化方法。

在设置/更新值时(setItem)会调用序列化方法,在取值时(getStoredValue)会调用反序列化方法。

function useStorageState<T>(key: string, options: Options<T> = {}) {
  // 序列化方法
  const serializer = (value: T) => {
    if (options.serializer) {
      return options.serializer(value)
    }
    return JSON.stringify(value)
  }

  // 反序列化方法
  const deserializer = (value: string): T => {
    if (options.deserializer) {
      return options.deserializer(value)
    }
    return JSON.parse(value)
  }

  // other code ...
}

缓存 key 变化时,自动更新状态

支持在缓存的 key 更新时,会自动的重新获取值更新 state。

useUpdateEffect 等同于 useEffect,但会忽略首次执行,只在依赖更新的时候执行一次。

//只有当 key 更新时,会自动的重新获取值更新 state
useUpdateEffect(() => {
  setState(getStoredValue())
}, [key])