jotai
一句话
原子化(atomic)的 React 状态管理库:把状态拆成一个个最小单元 atom,组件只订阅自己用到的 atom,天然细粒度、天然可组合,是 Recoil 思路的轻量化实现(作者也参与过 Recoil)。
核心心智模型
atom(initialValue)创建一个原子状态单元,本身不含值,只是一个"定义"(类似 key),值存在 Provider 内部的 store 里。useAtom(atom)类似useState,返回[value, setValue],组件订阅这个 atom,只有它变化才重渲染——粒度比 zustand 的 selector 更细,因为拆分单位是 atom 本身而不是手写的切片函数。- 派生状态用"读写函数"定义 atom:
const doubled = atom((get) => get(count) * 2),依赖关系自动追踪,类似 Excel 公式/信号(signals),不需要手动声明依赖数组。 - 默认需要
<Provider>(不加也能工作,走隐式的全局 store,但多实例/测试隔离场景建议显式加)。 - 支持 atom 之间任意组合、异步 atom(值是 Promise,配合 Suspense)、atomFamily(参数化生成一组 atom)。
最小示例
import { atom, useAtom } from 'jotai'
const countAtom = atom(0)
const doubledAtom = atom((get) => get(countAtom) * 2)
function Counter() {
const [count, setCount] = useAtom(countAtom)
const [doubled] = useAtom(doubledAtom)
return (
<button onClick={() => setCount((c) => c + 1)}>
{count} (doubled: {doubled})
</button>
)
}
什么时候选它
- 状态天然是"很多细碎的小状态"(表单字段、UI 局部开关、独立小组件状态),想要按需订阅而不用手写 selector。
- 需要大量派生/计算状态,且依赖关系会变化,想要类似 signals 的自动依赖追踪。
- 喜欢
useState的心智模型,只是想让它能跨组件共享。 - 需要和 Suspense/异步数据配合得很自然(atom 可以直接是异步的)。
什么时候不选它 / 替代方案
| 场景 | 更合适的选择 |
|---|---|
| 状态是少数几个"大块"(如整个 App 的 store),不需要拆得很细 | zustand(单一 store + selector 更直观) |
| 只是父子组件传值,层级不深 | 直接 props 或 useState |
| 强团队规范、需要 action 审计/时间旅行调试 | Redux Toolkit |
| 状态本质是服务端数据缓存 | TanStack Query |
与 zustand 的对比
| jotai | zustand | |
|---|---|---|
| 组织方式 | 自底向上,拆成很多 atom | 自顶向下,一个(或几个)集中 store |
| 订阅粒度 | 天然细粒度(按 atom) | 手写 selector 控制粒度 |
| 派生状态 | 声明式、自动依赖追踪 | 手动在 selector 或 store 里计算 |
| Provider | 建议加(隔离/测试),也可不加 | 不需要 |
| 心智负担 | 状态一多,atom 分散、需要良好组织 | store 集中,结构一目了然 |
一句话选择:状态零散、派生关系复杂 → jotai;状态相对集中、逻辑简单直接 → zustand。
优缺点
优点:细粒度订阅、无样板、派生状态声明式好维护、原生支持异步/Suspense、TS 类型推导好。
缺点:atom 一多容易散落各处不好管理(需要自己约定组织方式,比如按 feature 建 atoms 文件);调试时"值在哪里被改的"不如集中 store 直观;生态不如 Redux 成熟。
相关信息
- Star 数:21k+,pmndrs 系(同系还有 zustand、react-three-fiber)出品,维护活跃。