Published on

原子化状态管理方案-jotai

Authors
  • avatar
    Name
    noodles
    每个人的花期不同,不必在乎别人比你提前拥有

在之前的Form表单方案思考中,在实现表单方案上有一些思考:

  • 功能字段原子化
  • 设计生命周期能力
  • 结合当前业务情况去选型

在原子能力的理解上,原子是最小化的单元模块.它包含极简的功能,通过原子的组合(派生)来组合出更加复杂的功能模版.原子模块在复用性和功能扩展性上有一定的优势. jotai就是一种基于原子能力设计的状态管理方案.下面从jotai的简单使用入手进而梳理jotai的实现原理.在文末会与其他状态管理方案对比总结.

前置知识

WeakMap

在jotai中,使用WeakMap来做store数据 的存储.WeakMap的键值只能是对象或者非全局的Symbol对象,并且对对象的引用是弱引用,不能阻止垃圾回收机制.

jotai的简单使用

    // 定义一个atom值
    const countAtom = atom(0)
    export default function Home() {
    // 使用useAtom来获取atom值 相当于useState 在值变化的时候会自动触发组件渲染
        const [count, setCount] = useAtom(countAtom)
        return (
            <div
                onClick={() => setCount(count + 1)}
            >
            {count}
            </div>
        )
    }

jotai的实现原理

    const countAtom = atom(0)
    const doubledCountAtom = atom((get) => get(countAtom) * 2)
    const store = getDefaultStore()
    store.set(countAtom, 10)
    const doubledCountAtomValue = store.get(doubledCountAtom)
    // value = 20
    const countAtomValue = store.get(countAtom)
    // value = 10

上面的例子中基础的atom值countAtom、派生atom值doubledCountAtom,通过store的方式设置countAtom的值,最后发现派生的doubledCountAtom值也发生了 变化.下面就从这个例子出发了解jotai的值创建逻辑、更新逻辑的实现,最后看下jotai是如何与react结合实现组件的更新的.

值的创建

    // 创建atom值的入口函数
    // 通过函数重载, atom入口函数支持多种参数类型
    export function atom<Value, Args extends unknown[], Result>(
        read?: Value | Read<Value, SetAtom<Args, Result>>,
        write?: Write<Args, Result>,
    ) {
        // atom的唯一标识,在值更新依赖追踪都有用
        const key = `atom${++keyCount}`
        const config = {
            toString() {
            return import.meta.env?.MODE !== 'production' && this.debugLabel
                ? key + ':' + this.debugLabel
                : key
            },
        } as WritableAtom<Value, Args, Result> & { init?: Value | undefined }
        // read是函数时候处理逻辑 派生处理
        if (typeof read === 'function') {
            config.read = read as Read<Value, SetAtom<Args, Result>>
        } else {
            // 普通方式 处理逻辑
            config.init = read
            config.read = defaultRead
            config.write = defaultWrite as unknown as Write<Args, Result>
        }
        // 写派生处理逻辑
        if (write) {
            config.write = write
        }
        return config
    }
    // 省略若干代码
    // 创建store的函数功能
    const store: Store = {
        // 获取atom值
        get: (atom) => returnAtomValue(readAtomState(atom)),
        // 设置atom值
        set: (atom, ...args) => {
            try {
                return writeAtomState(atom, ...args)
            } finally {
                // 依赖更新处理逻辑
                recomputeInvalidatedAtoms()
                flushCallbacks()
            }
        },
        // 订阅atom值
        sub: (atom, listener) => {
            const mounted = mountAtom(atom)
            const listeners = mounted.l
            listeners.add(listener)
            flushCallbacks()
            return () => {
                listeners.delete(listener)
                unmountAtom(atom)
                flushCallbacks()
            }
        },
    }
    Object.defineProperty(store, BUILDING_BLOCKS, { value: buildingBlocks })
    return store

值的获取

在获取atom值的时候,执行store的get方法.
jotai获取值

值的设置

在设置atom值的时候,执行store的set方法.
jotai设置值

跟react的结合实现实现组件更新

在组件中,会使用useAtom来获取atom值,在atom值变化的时候会自动触发组件的更新. jotai组件更新

jotai实现上的一些思考

异步数据更新实现

jotai可以实现异步数据的使用,在实现上通过标志位的方式,非常巧妙.在工具包中也融合了一些常用的store使用方式.

增加测试调试能力入口

在调试包的时候,有很多内部能力暴露给外部,在设计功能包的时候可以考虑做类似功能的设计,增加测试、调试能力.

懒计算

当前atom的依赖发生更新的时候,只会更新依赖atom的版本.只有在重新获取当前atom的时候,才会重新计算当前atom的值

状态管理方案的一点思考

当我们聊状态管理的时候我们在聊什么中阅读了redux/mobx/zustand的源码实现,jotia似乎是比较 '另类'的状态管理方案.也许到了那个老生常谈的话题,如果真的要做状态管理方案的选型应该怎么选呢?!!! 也许应该多问自己几个问题:

  • 当前的业务是什么样的/未来的业务数据是怎么样的 比如数据轻量还是复杂度较高
  • 团队是否有学习成本 好的库会'教育'人,这种技术选型是否会在团队比较容易的形成最佳实践-团队的方言

参考

deepwiki-jotai