ahooks源码学习之useLocalStorageState & useSessionStorageState

1,391 阅读3分钟

useLocalStorageState & useSessionStorageState

简介

这两个hooks就是将 state 进行持久化存储在 localStorage/sessionStorage 中,我们仅需要查看一个hooks的用法即可。

useLocalStorageState demo 🔗

ahooks.js.org/zh-CN/hooks…

源码实现

useLocalStorageState

其源码文件 useLocalStorageState/index.ts 如下

import { createUseStorageState } from "../createUseStorageState";
import isBrowser from "../utils/isBrowser";

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

export default useLocalStorageState;

useSessionStorageState

其源码文件 useSessionStorageState/index.ts 如下

import { createUseStorageState } from "../createUseStorageState";
import isBrowser from "../utils/isBrowser";

const useSessionStorageState = createUseStorageState(() =>
  isBrowser ? sessionStorage : undefined
);

export default useSessionStorageState;

如果当前使用环境为浏览器的话,我才能使用这两个hooks

const isBrowser = !!(
  typeof window !== 'undefined' &&
  window.document &&
  window.document.createElement
);

export default isBrowser;

发现这两个 hooks 的实现都是基于 createUseStorageState,因此我们只需要研读该方法的实现即可 如果阅读源码期间遇到别的 hooks (useMemoizedFn, useUpdateEffect),请参考对应文档详解,这里仅提供简单说明

createUseStorageState

useSessionStorageState/index.ts

// 为了简化内容,去掉了部分Typescript定义
export function createUseStorageState(getStorage: () => Storage | undefined) {
  /***
   * useStorageState 传递参数
   * key:持久化存储的key,必须
   * options:{ // 可选
   *    defaultValue: , 默认值,可选
   *    serializer: (v) => v, 序列化函数,存值时候使用,可选
   *    deserializer: (v) => v, 反序列化函数,取值时候使用,可选
   * }
   */
  function useStorageState<T>(key: string, options?: Options<T> & OptionsWithDefaultValue<T>) {
    let storage: Storage | undefined; // storage的实例,localStorage | sessionStorage

    // https://github.com/alibaba/hooks/issues/800
    // 上述链接是关于,如果浏览器禁用cookies,那么该hooks将会报错,因为禁用了cookies,浏览器会提示 "SecurityError: Failed to read the 'localStorage' property from 'Window': Access is denied for this document." 即禁止该网站使用浏览器存储数据,以下try catch就是关于这个issue的修复代码
    try {
      storage = getStorage();
    } catch (err) {
      console.error(err);
    }

    // 序列化函数,用户可以传递自己的序列化函数
    // 默认是 JSON.stringify
    const serializer = (value: T) => {
      if (options?.serializer) {
        return options?.serializer(value);
      }
      return JSON.stringify(value);
    };

    // 反序列化函数,用户可以传递自己的反序列化函数
    // 默认是 JSON.parse
    const deserializer = (value: string) => {
      if (options?.deserializer) {
        return options?.deserializer(value);
      }
      return JSON.parse(value);
    };

    // 在取数据的时候,先查看本地存储中是否有该key的数据
    // 如果存在则使用反序列化函数进行解析后返回
    // 如果不存在,则返回用户提供的默认值
    // 如果该默认值是函数,则执行该函数,将结果返回
    function getStoredValue() {
      try {
        const raw = storage?.getItem(key);
        if (raw) {
          return deserializer(raw);
        }
      } catch (e) {
        console.error(e);
      }
      if (isFunction(options?.defaultValue)) {
        return options?.defaultValue();
      }
      return options?.defaultValue;
    }

    const [state, setState] = useState<T | undefined>(() => getStoredValue());

    // useUpdateEffect: 使用上与 useEffect 完全相同,只是它忽略了首次执行,只在依赖项更新时执行。
    useUpdateEffect(() => {
      setState(getStoredValue());
    }, [key]);

    // 更新操作
    // 如果用户在赋值函数中传入undefined值,则默认清空本地存储中的该 item
    // 如果用户传入一个方法,则将老值当作参数,调用改方法后得到新值,将新值序列化之后进行吃持久化存储
    // 如果是其他值,则直接更新值,再将新值序列化之后进行吃持久化存储
    const updateState = (value?: T | IFuncUpdater<T>) => {
      if (typeof value === 'undefined') {
        setState(undefined);
        storage?.removeItem(key);
      } else if (isFunction(value)) {
        const currentState = value(state);
        try {
          setState(currentState);
          storage?.setItem(key, serializer(currentState));
        } catch (e) {
          console.error(e);
        }
      } else {
        try {
          setState(value);
          storage?.setItem(key, serializer(value));
        } catch (e) {
          console.error(e);
        }
      }
    };

    // 返回值和更新函数
    // useMemoizedFn: 持久化 function 的 Hook,理论上,可以使用 useMemoizedFn 完全代替 useCallback。
    return [state, useMemoizedFn(updateState)];
  }
  return useStorageState;
}