当前位置:网站首页>如何封装 cookie/localStorage/sessionStorage hook?
如何封装 cookie/localStorage/sessionStorage hook?
2022-08-01 20:58:00 【GopalFeng】
本文是深入浅出 ahooks 源码系列文章的第九篇,这个系列的目标主要有以下几点:
- 加深对 React hooks 的理解。
- 学习如何抽象自定义 hooks。构建属于自己的 React hooks 工具库。
- 培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。
今天来看看 ahooks 是怎么封装 cookie/localStorage/sessionStorage 的。
cookie
ahooks 封装了 useCookieState,一个可以将状态存储在 Cookie 中的 Hook 。
该 hook 使用了 js-cookie[1] 这个 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[2] 中可以看到 cookie 被 disabled 的时候,也是访问不了 localStorage 的。stackoverflow[3] 也有这个讨论。(奇怪的知识又增加了)
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 的封装整体比较简单,大家可以参考借鉴。
系列文章:
- 大家都能看得懂的源码(一)ahooks 整体架构篇[4]
- 如何使用插件化机制优雅的封装你的请求hook [5]
- ahooks 是怎么解决 React 的闭包问题的?[6]
- ahooks 是怎么解决用户多次提交问题?[7]
- ahooks 中那些控制“时机”的hook都是怎么实现的?[8]
- 如何让 useEffect 支持 async...await?[9]
- 如何让定时器在页面最小化的时候不执行?[10]
- 记录第一次给开源项目提 PR[11]
参考资料
[1]js-cookie: https://www.npmjs.com/package/js-cookie
[2]issue: https://github.com/alibaba/hooks/issues/800
[3]stackoverflow: https://stackoverflow.com/questions/26550770/can-session-storage-local-storage-be-disabled-and-cookies-enabled
[4]大家都能看得懂的源码(一)ahooks 整体架构篇: https://juejin.cn/post/7105396478268407815
[5]如何使用插件化机制优雅的封装你的请求hook : https://juejin.cn/post/7105733829972721677
[6]ahooks 是怎么解决 React 的闭包问题的?: https://juejin.cn/post/7106061970184339464
[7]ahooks 是怎么解决用户多次提交问题?: https://juejin.cn/post/7106461530232717326
[8]ahooks 中那些控制“时机”的hook都是怎么实现的?: https://juejin.cn/post/7107189225509879838
[9]如何让 useEffect 支持 async...await?: https://juejin.cn/post/7108675095958126629
[10]如何让定时器在页面最小化的时候不执行?: https://juejin.cn/post/7109399243202232357
[11]记录第一次给开源项目提 PR: https://juejin.cn/post/7110144695098933284
边栏推荐
猜你喜欢
![[Multi-task model] Progressive Layered Extraction: A Novel Multi-Task Learning Model for Personalized (RecSys'20)](/img/7d/f8f150ad13f4cacc143491fcd8420b.png)
[Multi-task model] Progressive Layered Extraction: A Novel Multi-Task Learning Model for Personalized (RecSys'20)

【Kaggle】Classify Leaves

Digital twin Beijing the imperial palace, yuan universe is the process of tourism

Questions I don't know in database kernel interview(1)

Fork/Join线程池

idea插件generateAllSetMethod一键生成set/get方法以及bean对象转换

算法---解码方法(Kotlin)

乐观锁批量跟新 纯SQL

Remove 360's detection and modification of the default browser

微信小程序云开发|个人博客小程序
随机推荐
Protocol Buffer usage
Godaddy domain name resolution is slow and how to use DNSPod resolution to solve it
虚拟机的IP地址自动变为127.0.0.1
[译] 容器和 Kubernetes 中的退出码完整指南
useful website
StringTable详解 串池 性能调优 字符串拼接
Internet使用的网络协议是什么
扣减库存方案
360借条安全专家:陌生微信好友不要轻易加贷款推广多是诈骗
tiup mirror init
excel高级绘图技巧100讲(二十二)-如何对不规则数据进行分列
Buttons with good user experience should not have hover state on mobile phones
Zheng Xiangling, Chairman of Tide Pharmaceuticals, won the "2022 Outstanding Influential Entrepreneur Award" Tide Pharmaceuticals won the "Corporate Social Responsibility Model Award"
【微信小程序】【AR】threejs-miniprogram 安装(76/100)
函数(二)
C语言之字符串函数二
模板特例化和常用用法
[Energy Conservation Institute] Ankerui Food and Beverage Fume Monitoring Cloud Platform Helps Fight Air Pollution
iptables的使用简单测试
[Energy Conservation Institute] Comparative analysis of smart small busbar and column head cabinet solutions in data room