理解何时使用 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 来存储 状态的值。
代码 运行如下所示, 如果你在文本框输入一些内容,文本会在下面展示,并展示这个文本的长度:
现在,让我们把这个 例子 转化 为 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。