第十一章 这三个全局状态管理库之间的共性与差异 【中】

73 阅读4分钟

理解何时使用 Jotai 和 Reocil

Jotai 的 API 受到了 Recoil 很多 启发。在本小节,我们会 把 一个 Recoil 的 例子 转变为 Jotai的 例子,并 比较 这 两者的不同。

让我们从 引入 Recoil 的基本 API 开始

import {
    RecoilRoot,
    atom,
    selector
    useRecoilState,
    useRecoilValue
} from "recoil";

首先,我们要用atom 函数 创建 text 字符串的 状态:

const textState = atom({
    key: 'textState',
    default: ''
})

这个atom 函数 接收了两个参数,key 字符串 和 默认值。

然后,我们通过 useRecoilState 钩子来获取 这个 状态:

const TextInput = () => {
    const [text, setText] = useRecoilState(textState);
    return (
        <div>
            <input
                type="text"
                value={text}
                onChagne{(event) => {
                    setText(event.targe.value);
                })
            />
            <br />
           Echo: {text}
        </div>
    )
}

useRecoilState的返回值 和 useState 一样。因此,下面的代码就很好懂了。

第二个要定义的 状态 是 衍生状态。我们可以使用 selector 来 定义 衍生状态:

const charCountState = selector({
    key: "charCountState",
    get: ({ get }) => get(textState).length
})

这个selector函数接收两个参数,key 字符串 和 get 函数。这个 get 属性 是用来 返回 衍生值 的 函数。而在get 函数里 的 get函数,则用于 返回 对应atom 的状态。。

为了使用这个衍生状态,我们需要使用 useRecoildValue 钩子:

const CharacterCount = () => {
    const count = useRecoildValue(charCounterState);
    return <>Character Count: {count}</>
}

这个组件会在 textState变化时 而 重新渲染,因为charCountState 是由 textState 衍生而来的。

CharacterCounter组件,则是通过 组合 这两个组件来 定义的:

const CharacterCounter = () => {
    <div>
        <TextInput />
        <CharacterCount />
    </div>
}

最后,我们要定义 App 组件:

const App = () => {
    <RecoilRoot>
        <CharacterCounter />
    </RecoilRoot>
}

App组件中,我们使用 RecoilRoot 来存储 状态的值。

代码 运行如下所示, 如果你在文本框输入一些内容,文本会在下面展示,并展示这个文本的长度:

image.png

现在,让我们把这个 例子 转化 为 Jotai 风格的 代码。

首先,我们要从 Jotai 引入 基本 函数:

import { atom, useAtom } from "jotai";

Jotai 的 API 的设计 风格,就是 最小化。

第一个atom 用于 存储 文本:

const textAtom = atom("");

这段代码和 Recoil 看起来差不多,除了没有 默认值外,因为 Jotai 并不要求 key 关键字。

为了使用 这个定义好的 atom,我们要用 useAtom 函数:

const TextInput () => {
    const [text, setText] = useAtom(textAtom);
    
    return (
        <div>
            <input
                type="text"
                value={text}
                onChange={(event) => {
                    setText(event.target.value);
                }}
            />
            Echo: {text}
        </div>
    )
}

这个useAtom钩子,和useState类似。所以大家使用useAtom时,并不会觉得陌生。

第二个要定义的atom是一个 衍生的 atom:

const charCountAtom = atom((get) => get(textAtom).length);

至于使用第二个atom,只要使用useAtom函数即可:

const CharacterCount = () => {
    const [count] = useAtom(charCountAtom);
    return <>Character Count: {count} </>;
}

我们要通过[count] 来获取返回值。除此以外,这段代码和 Recoil 代码 大致相同。

之后,我们要定义CharacterCounter组件:

const CharacterCounter = () => {
    <div>
        <TextInput />
        <CharacterCount />
    </div>
}

最后,我们可以定义 App 组件:

const App = () => {
    <>
        <CharacterCounter />
    </>
}

像这样一个最小化应用Jotai的 代码,还不需要 使用 Provider 组件。

从 Recoil 示例转换到 Jotai 示例主要是语法上的变化,行为是完全相同的。

接下来,让我们讨论这两者的不同。

比较Recoil 和 Jotai的不同

事实上,这两个库在特性上有很大的不同,我们在此只局限于本例子的不同:

  • 最大的区别在于是否需要使用 key 字符串。开发 Jotai 的一大动机就是省略 key 字符串。得益于这一特性,Recoil 中的原子定义如 atom({ key: "textState", default: "" }),在 Jotai 中可以简化为 atom("")。技术上这看起来很简单,但对开发者体验却有很大影响。命名在编程中是一件困难的事情,尤其是 key 属性必须唯一。在实现上,Jotai 使用了 WeakMap,依赖于原子对象的引用;而 Recoil 则是基于 key 字符串,不依赖对象引用。key 字符串的好处是可以序列化,这有利于实现持久化功能,因为持久化需要序列化。Jotai 则需要一些技巧来克服序列化的问题。

  • 另一个与 key 字符串相关的区别是统一的 atom 函数。在 Jotai 中,atom 函数既可用于原子,也可用于 Recoil 中的 selector(选择器)。然而,这也存在一个缺点:它无法完全表达所有用例,有时需要其他 Jotai 函数来支持其他需求。

  • 最后,Jotai 的无 provider 模式(即可以省略 Provider 组件)在技术上很简单,但非常有利于降低开发者使用库时的心理门槛,让上手更加友好。

Recoil 和 Jotai 的基本功能是相同的,开发者需要根据其他需求或者仅仅是对于 API 的偏好做出选择。Jotai 的 API 非常简洁,与 Zustand 类似。

在这个小节,我们对比了 Recoil 和 Jotai的异同。接下来,我们要比较 MobX 和 Valtio。