Jotai 实战(上)

早前,我们介绍了《从 RecoilJotai》 的迁移。其实随着迁移的过程中,也有着一些实践性的好方法,这里做一个总结提炼。

文章中的代码地址:github.com/bigbigDream... 快捷传送门

大纲

  1. [atom 细粒度作用域控制](#atom 细粒度作用域控制 "###1")
  2. [atom 懒请求](#atom 懒请求 "###2")
  3. [atom 本地缓存](#atom 本地缓存 "###3")
  4. [atom 动态 key](#atom 动态 key "###4")

1. atom 细粒度作用域控制

目的:实现不同层级的状态管理,例如:组件层页面层应用层 的细粒度控制。

实现方案

在不同的作用域层级添加 Provider ,作为隔离效果,还有一个好处是:当重新加载时,挂在 Provider 的所有 Atom 会被重置状态。后者才是最为好用的一个能力。

tsx 复制代码
import { Provider } from 'jotai'

const Component: FC = ({ children }) => <Provider>{ children }</Provider>

2. atom 懒请求

目的:在一个方法中,同时做到 atomgetset ,避免写过多的样板代码。

这里写了一个 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 来实现降级,但是内部我们做了最终降级,假设 IndexDBwebSQLLocalStorage 都不支持,我们最终降级到 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 来实现 paramsatom ,这其实对于 fetch atom 很友好,可以实现不同参数的 fetch,尤其是 url params 请求。

但是 atomFamily 我们在 下篇介绍过,会存在内存泄漏的风险,因为内部使用了 Map 作为不自动回收的存储数据结构。

所以我们借助了 setShouldRemove 在页面 卸载 时,进行全量清理。


好了,Jotai BPs 上篇 到这里就结束了。

我是 不换,如果写的不正确,欢迎评论区批评指正,如果对你有帮助,请点个小赞,赠人玫瑰,手有余香。我们下期再见👋。

相关推荐
mldong3 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排3 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60614 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角5 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!5 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰5 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi7 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen7 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马8 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu8 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss