早前,我们介绍了《从 Recoil
到 Jotai
》 的迁移。其实随着迁移的过程中,也有着一些实践性的好方法,这里做一个总结提炼。
文章中的代码地址:github.com/bigbigDream... 快捷传送门
大纲
- [
atom
细粒度作用域控制](#atom 细粒度作用域控制 "###1") - [
atom
懒请求](#atom 懒请求 "###2") - [
atom
本地缓存](#atom 本地缓存 "###3") - [
atom
动态key
](#atom 动态 key "###4")
1. atom
细粒度作用域控制
目的:实现不同层级的状态管理,例如:
组件层
、页面层
、应用层
的细粒度控制。
实现方案
在不同的作用域层级添加 Provider
,作为隔离效果,还有一个好处是:当重新加载时,挂在 Provider
的所有 Atom
会被重置状态。后者才是最为好用的一个能力。
tsx
import { Provider } from 'jotai'
const Component: FC = ({ children }) => <Provider>{ children }</Provider>
2. atom
懒请求
目的:在一个方法中,同时做到
atom
的get
和set
,避免写过多的样板代码。
这里写了一个 demo
,大概就是每次滚动到底部时,去主动触发一次请求。
实现方案
这里仅列举最核心的伪代码,减轻阅读负担。
ts
// some code
const loadMoreData = useAtomCallback(useCallback(async (get, set) => {
const _data = get(globalData);
await sleep(1500);
set(globalData, {data: [..._data.data, ...genUsers()]})
message.success("data load success!")
}, []));
useEffect(() => {
loadMoreData()
}, [])
可以看到,我们借助了 useAtomCallback
来实现这个能力,而 useCallback
的作用是为了维持 useAtomCallback
内部的函数引用唯一,避免加载 死循环。
重点:使用 useAtomCallback
时,必须在整个组件上下文中去 subscribe atom
,简单点就是 useAtom
或者 useAtomValue
,否则无法 get
最新的内容。
3. atom
本地缓存
目的:实现 本地缓存 和
atom
结合。
这里写了一个 demo
,目的是为了将 二维码
的地址,持久化到 IndexDB
,同时满足 f(UI)
的需要。
实现方案
store.ts
我们借助 localforage
来实现降级,但是内部我们做了最终降级,假设 IndexDB
、webSQL
、LocalStorage
都不支持,我们最终降级到 memory map
中,保证程序运转是正常的。
ts
import { atomWithStorage } from 'jotai/utils';
import localforage from "localforage";
import to from 'await-to-js';
const localforageInstance = localforage.createInstance({
storeName: 'LocalPageStore',
version: 1
})
const memoryCache = new Map();
const localForageStore: Parameters<typeof atomWithStorage>[2] = {
async setItem(key, value) {
const [err] = await to(localforageInstance.setItem(key, value));
if(err) {
memoryCache.set(key, value);
}
},
async getItem(key) {
const [err, result] = await to(localforageInstance.getItem(key));
if(err) {
return memoryCache.get(key) || null;
}
return result
},
async removeItem(key) {
const [err] = await to(localforageInstance.removeItem(key));
if(err) {
memoryCache.delete(key);
}
}
}
export const pageStorageStore = atomWithStorage('LocalCache', null, localForageStore)
4. atom
动态 key
目的:实现一个
atom
引用,可以作用多个atom
的读写。
这里写了一个 demo
,拆分两个 atom
,一个控制 checked
,一个控制 文案的显示
。
实现方案
store.ts
ts
import { atomFamily } from 'jotai/utils';
import {WritableAtom, atom} from "jotai";
const keyMapAtom: {
[StoreKey.CHECK_STORE]: WritableAtom<boolean, boolean[], void>;
[StoreKey.CONTENT_STORE]: WritableAtom<string, string[], void>
} = {
CHECK_STORE: atom(false),
CONTENT_STORE: atom('')
}
export const pageStore = atomFamily<StoreKey, WritableAtom<boolean, boolean[], void> | WritableAtom<string, string[], void>>(key => keyMapAtom[key]);
export enum StoreKey {
CHECK_STORE = 'CHECK_STORE',
CONTENT_STORE = 'CONTENT_STORE'
}
index.tsx
ts
// checked atom
const [checked, setChecked] = useAtom<boolean, [boolean], void>(pageStore(StoreKey.CHECK_STORE) as WritableAtom<boolean, boolean[], void>)
// content atom
const [content, setContent] = useAtom<string, [string], void>(pageStore(StoreKey.CONTENT_STORE) as WritableAtom<string, string[], void>)
const handleCheck = () => {
setChecked(!checked)
if(checked) {
setContent("Not Checked")
} else {
setContent("Checked")
}
}
const checkContainerCls = classes('check-container', {
'check-container-switch': checked
})
// memory GC
useEffect(() => () => {
pageStore.setShouldRemove(() => true);
})
我们可以借助 atomFamily
来实现 params
化 atom
,这其实对于 fetch atom
很友好,可以实现不同参数的 fetch,尤其是 url params
请求。
但是 atomFamily
我们在 下篇介绍过,会存在内存泄漏的风险,因为内部使用了 Map
作为不自动回收的存储数据结构。
所以我们借助了 setShouldRemove
在页面 卸载 时,进行全量清理。
好了,Jotai BPs
上篇 到这里就结束了。
我是 不换,如果写的不正确,欢迎评论区批评指正,如果对你有帮助,请点个小赞,赠人玫瑰,手有余香。我们下期再见👋。