本文是深入浅出 ahooks 源码系列文章的第九篇,该系列已整理成文档-地址。觉得还不错,给个 star 支持一下哈,Thanks。

今天来看看 ahooks 是怎么封装 cookie/localStorage/sessionStorage 的。

cookie

ahooks 封装了 useCookieState,一个可以将状态存储在 Cookie 中的 Hook 。

该 hook 使用了 js-cookie 这个 npm 库。我认为选择它的理由有以下:

  • 包体积小。压缩后小于 800 字节。自身是没有其它依赖的。这对于原本就是一个工具库的 ahooks 来讲是很重要的。
  • 更好的兼容性。支持所有的浏览器。并支持任意的字符。

当然,它还有其他的特点,比如支持 ESM/AMD/CommonJS 方式导入等等。

封装的代码并不复杂,先看默认值的设置,其优先级如下:

  • 本地 cookie 中已有该值,则直接取。
  • 设置的值为字符串,则直接返回。
  • 设置的值为函数,执行该函数,返回函数执行结果。
  • 返回 options 中设置的 defaultValue。
const [state, setState] = useState<State>(() => {
// 假如有值,则直接返回
const cookieValue = Cookies.get(cookieKey); if (isString(cookieValue)) return cookieValue;
// 定义 Cookie 默认值,但不同步到本地 Cookie
// 可以自定义默认值
if (isFunction(options.defaultValue)) {
return options.defaultValue();
} return options.defaultValue;
});

再看设置 cookie 的逻辑 —— updateState 方法。

  • 在使用 updateState 方法的时候,开发者可以传入新的 options —— newOptions。会与 useCookieState 设置的 options 进行 merge 操作。最后除了 defaultValue 会透传给 js-cookie 的 set 方法的第三个参数。
  • 获取到 cookie 的值,判断传入的值,假如是函数,则取执行后返回的结果,否则直接取该值。
  • 如果值为 undefined,则清除 cookie。否则,调用 js-cookie 的 set 方法。
  • 最终返回 cookie 的值以及设置的方法。
// 设置 Cookie 值
const updateState = useMemoizedFn(
(
newValue: State | ((prevState: State) => State),
newOptions: Cookies.CookieAttributes = {},
) => {
const { defaultValue, ...restOptions } = { ...options, ...newOptions };
setState((prevState) => {
const value = isFunction(newValue) ? newValue(prevState) : newValue;
// 值为 undefined 的时候,清除 cookie
if (value === undefined) {
Cookies.remove(cookieKey);
} else {
Cookies.set(cookieKey, value, restOptions);
}
return value;
});
},
); return [state, updateState] as const;

localStorage/sessionStorage

ahooks 封装了 useLocalStorageState 和 useSessionStorageState。将状态存储在 localStorage 和 sessionStorage 中的 Hook 。

两者的使用方法是一样的,因为官方都是用的同一个方法去封装的。我们以 useLocalStorageState 为例。

可以看到 useLocalStorageState 其实是调用 createUseStorageState 方法返回的结果。该方法的入参会判断是否为浏览器环境,以决定是否使用 localStorage,原因在于 ahooks 需要支持服务端渲染。

import { createUseStorageState } from '../createUseStorageState';
import isBrowser from '../utils/isBrowser'; const useLocalStorageState = createUseStorageState(() => (isBrowser ? localStorage : undefined)); export default useLocalStorageState;

我们重点关注一下,createUseStorageState 方法。

  • 先是调用传入的参数。假如报错会及时 catch。这是因为:

    • 这里返回的 storage 可以看到其实可能是 undefined 的,后面都会有 catch 的处理。
    • 另外,从这个 issue 中可以看到 cookie 被 disabled 的时候,也是访问不了 localStorage 的。stackoverflow 也有这个讨论。(奇怪的知识又增加了)
export function createUseStorageState(getStorage: () => Storage | undefined) {
function useStorageState<T>(key: string, options?: Options<T>) {
let storage: Storage | undefined;
// https://github.com/alibaba/hooks/issues/800
try {
storage = getStorage();
} catch (err) {
console.error(err);
}
// 代码在后面讲解
}
  • 支持自定义序列化方法。没有则直接 JSON.stringify。
  • 支持自定义反序列化方法。没有则直接 JSON.parse。
  • getStoredValue 获取 storage 的默认值,如果本地没有值,则返回默认值。
  • 当传入 key 更新的时候,重新赋值。
// 自定义序列化方法
const serializer = (value: T) => {
if (options?.serializer) {
return options?.serializer(value);
}
return JSON.stringify(value);
}; // 自定义反序列化方法
const deserializer = (value: string) => {
if (options?.deserializer) {
return options?.deserializer(value);
}
return JSON.parse(value);
}; 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()); // 当 key 更新的时候执行
useUpdateEffect(() => {
setState(getStoredValue());
}, [key]);

最后是更新 storage 的函数:

  • 如果是值为 undefined,则 removeItem,移除该 storage。
  • 如果为函数,则取执行后结果。
  • 否则,直接取值。
// 设置 State
const updateState = (value?: T | IFuncUpdater<T>) => {
// 如果是 undefined,则移除选项
if (isUndef(value)) {
setState(undefined);
storage?.removeItem(key);
// 如果是function,则用来传入 state,并返回结果
} 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);
}
}
};

总结与归纳

对 cookie/localStorage/sessionStorage 的封装是我们经常需要去做的,ahooks 的封装整体比较简单,大家可以参考借鉴。

本文已收录到个人博客中,欢迎关注~

大家都能看得懂的源码 - 如何封装 cookie/localStorage/sessionStorage hook?的更多相关文章

  1. 大家都能看得懂的源码 - 那些关于DOM的常见Hook封装(一)

    本文是深入浅出 ahooks 源码系列文章的第十四篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 上一篇我们探讨了 ahooks 对 DOM 类 Hooks 使用 ...

  2. 大家都能看得懂的源码 - 那些关于DOM的常见Hook封装(二)

    本文是深入浅出 ahooks 源码系列文章的第十五篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 本篇接着针对关于 DOM 的各个 Hook 封装进行解读. us ...

  3. 大家都能看得懂的源码 - ahooks useSet 和 useMap

    本文是深入浅出 ahooks 源码系列文章的第十篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 今天我们来聊聊 ahooks 中对 Map 和 Set 类型进行状 ...

  4. 大家都能看得懂的源码(一)ahooks 整体架构篇

    本文是深入浅出 ahooks 源码系列文章的第一篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 第一篇主要介绍 ahooks 的背景以及整体架构. React h ...

  5. 大家都能看得懂的源码 - ahooks 是怎么处理 DOM 的?

    本文是深入浅出 ahooks 源码系列文章的第十三篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 本篇文章探讨一下 ahooks 对 DOM 类 Hooks 使用 ...

  6. 大家都能看得懂的源码之ahooks useInfiniteScroll

    本文是深入浅出 ahooks 源码系列文章的第十七篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 简介 useInfiniteScroll 封装了常见的无限滚动逻 ...

  7. 大家都能看得懂的源码之 ahooks useVirtualList 封装虚拟滚动列表

    本文是深入浅出 ahooks 源码系列文章的第十八篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 简介 提供虚拟化列表能力的 Hook,用于解决展示海量数据渲染时 ...

  8. 如何读懂Framework源码?如何从应用深入到Framework?

    如何读懂Framework源码? 首先,我也是一个应用层开发者,我想大部分有"如何读懂Framework源码?"这个疑问的,应该大都是应用层开发. 那对于我们来讲,读源码最大的问题 ...

  9. <转>如何高效快速看懂Android源码

    原网址:http://jingyan.baidu.com/article/574c5219ca78ed6c8d9dc12a.html 在Android系统上工作了一段时间,经常会遇到题目中的问题,下面 ...

随机推荐

  1. HTML行内元素与块级元素有哪些及区别详解

    转自 https://www.jb51.net/web/724286.html   这篇文章主要介绍了HTML行内元素与块级元素有哪些及区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具 ...

  2. transforms.py

    from PIL import Image from torchvision import transforms #python的用法-->tensor数据类型 #通过transforms.To ...

  3. ICDAR2013

    参考:https://www.cnblogs.com/dmyu/p/6483357.html以及博主相关文章等.

  4. mysql中max_connections与max_user_connections使用区别

    问题描述:把max_connections和max_user_connections参数进行分析测试,顾名思义,max_connections就是负责数据库全局的连接数,max_user_connec ...

  5. .NET中的迭代器(Iterator)

    更新记录 本文迁移自Panda666原博客,原发布时间:2021年6月30日. 一.迭代器介绍 C#2.0开始,我们可以使用迭代器(iterator).编译器自动把我们定义的迭代器生成 可枚举类型 或 ...

  6. 『忘了再学』Shell基础 — 32、Shell中test测试命令详解

    目录 1.test测试命令 (1)test命令介绍 (2)test命令使用方式 (3)示例 2.按照文件类型进行判断 3.按照文件权限进行判断 4.两个文件之间进行比较 5.两个整数之间比较 6.字符 ...

  7. Leetcode 1051. 高度检查器

    这题的目的是找出排序后和排序前位置不同的元素的个数 正常通过复制出一个新的数组,然后对比排序后的数组就能做出,但是时间是1ms 然后发现一种基于桶排序来计数的做法 public int heightC ...

  8. UiPath循环活动Do While的介绍和使用

    一.Do While的介绍 先执行循环体, 再判断条件是否满足, 如果满足, 则再次执行循环体, 直到判断条件不满足, 则跳出循环 二.Do While在UiPath中的使用 1. 打开设计器,在设计 ...

  9. UiPath条件判断活动Flow Decision的介绍与使用

    一.Flow Decision介绍 FlowDecision节点是一个条件节点,它根据指定条件是否成立来控制流程的两个分支. 当条件为True时,流程执行一个分支 当条件为False时,流程执行另外一 ...

  10. python小题目练习(四)

    题目:JAVA和Python实现冒泡排序 实现代码: # Java实现对数组中的数字进行冒泡排序scoreList = [98, 87, 89, 90, 69, 50]temp = 0for i in ...