React Hooks 的分析 - useLocalStorage 与 useSessionStorage (ahooks)
- Published on
- — 1211 Words
- Authors
- Name
- Richard Shih
- @realshiddong
ahooks 实现分析
代码实现:useLocalStorageState、useSessionStorageState、createUseStorageState
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.localStorage
或window.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
的存储结果可能为传入的类型 T
或 undefined
,其中隐式的表示了未传递 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.stringify
和 JSON.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])