当应用状态复杂度超过 useState + useContext 的承载能力,但又不至于引入 Redux 这类重型方案时,轻量级状态库是极佳的选择。它们通常体积小、API 简洁、学习成本低,同时提供了细粒度更新、状态派生、异步处理等实用能力。
13.3.1 Zustand:极简 API、无 Provider 的状态管理
Zustand(德语“状态”)是一个基于 Hook 的轻量状态管理库,核心理念是 “状态就是一份普通的 JavaScript 对象,你可以直接修改它”。它不需要 Provider 包裹,也不需要 dispatch action,用起来几乎零模板代码。
基本用法:
npm install zustand
import { create } from 'zustand';
// 定义一个 store
const useBearStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}));
在组件中使用:
function BearCounter() {
const bears = useBearStore((state) => state.bears);
return <h1>{bears} 只熊</h1>;
}
function Controls() {
const increase = useBearStore((state) => state.increasePopulation);
return <button onClick={increase}>增加一只</button>;
}
Zustand 的 set 方法会合并状态(类似 setState),可以直接传入新对象,也可以传入一个基于旧状态的函数。选择器(selector)机制确保了组件的精确更新——只有选中的状态变化时,组件才会重新渲染。
处理异步操作:
const useUserStore = create((set) => ({
user: null,
loading: false,
fetchUser: async (id) => {
set({ loading: true });
const res = await fetch(`/api/user/${id}`);
const user = await res.json();
set({ user, loading: false });
},
}));
异步逻辑就写在 store 的创建函数中,和同步操作一样自然,不需要额外的中间件(当然 Zustand 也支持中间件,例如 persist 持久化)。
核心优势:
- 体积极小:gzip 后仅约 1KB。
- 无 Provider:直接 import store 的 hook 即可在任意组件中使用,组件树无需包裹。
- 选择器更新:默认使用
Object.is比较,可自定义shallow比较器以避免不必要渲染。 - 灵活的结构:可以将多个相关状态集中在一个 store,也可以拆分多个小 store,完全自由。
适用场景: 中小型应用、需要跨组件共享状态的场景,特别适合追求极简架构的开发者。
13.3.2 Jotai:原子化状态管理
Jotai(日语“状态”)受 Recoil 启发,采用原子化(Atom) 的状态管理模式。状态被拆分成一个个独立的原子,每个原子可以被任意组件读取和写入,原子之间可以相互派生,更新只会影响依赖它的原子和组件,实现极度细粒度的重渲染控制。
基本用法:
npm install jotai
import { atom, useAtom } from 'jotai';
// 定义一个原子
const countAtom = atom(0);
function Counter() {
const [count, setCount] = useAtom(countAtom);
return (
<div>
{count}
<button onClick={() => setCount(c => c + 1)}>+1</button>
</div>
);
}
派生原子(Derived Atoms):
const doubledAtom = atom((get) => get(countAtom) * 2);
function DoubledDisplay() {
const [doubled] = useAtom(doubledAtom);
return <div>{doubled}</div>;
}
派生原子是只读的,它的值随着依赖的原始原子自动更新。
异步原子:
const userAtom = atom(async () => {
const res = await fetch('/api/user');
return res.json();
});
function User() {
const [user] = useAtom(userAtom);
// 初次渲染时 user 是 undefined,可用 Suspense 或自行处理加载态
return <div>{user?.name}</div>;
}
Jotai 支持 Suspense,可以在异步原子就绪前显示 fallback。
核心优势:
- 极致细粒度更新:状态拆分到原子级别,只有依赖某个原子的组件才会重渲染。
- 组合灵活:原子可以自由组合派生新的原子,构建复杂数据流仍保持清晰。
- Provider 可选:默认使用全局 store,也可以手动提供 Provider 实现局部隔离或测试。
- Suspense 集成:原生支持异步原子与 Suspense。
适用场景: 需要极高渲染性能的大型应用,或者喜欢函数式、原子化设计思想的状态管理场景。
13.3.3 Recoil:原子化状态与派生状态
Recoil 同样是 Facebook 开发的原子化状态管理库,提供了 atom 和 selector 概念。不过需注意,Recoil 当前仍属于实验性项目,版本更新较慢,社区活跃度不如 Zustand 和 Jotai。此处仅做简要介绍。
基本用法:
import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';
const textState = atom({
key: 'textState',
default: '',
});
const charCountState = selector({
key: 'charCountState',
get: ({get}) => {
const text = get(textState);
return text.length;
},
});
function TextInput() {
const [text, setText] = useRecoilState(textState);
return <input value={text} onChange={(e) => setText(e.target.value)} />;
}
function CharCount() {
const count = useRecoilValue(charCountState);
return <div>字符数: {count}</div>;
}
Recoil 必须使用 RecoilRoot 包裹整个应用,每个 atom/selector 需要一个全局唯一的 key,这在大型应用中可能显得有些啰嗦。
核心特点:
- 原生 React 思维:API 与
useState非常相似。 - 数据流图(Data Flow Graph):状态之间的关系是可追踪、可调试的。
- 并发特性:支持 React 18 并发模式。
但鉴于其实验性质和相对较高的复杂度,新项目通常更推荐 Zustand 或 Jotai(Jotai 本身就是 Recoil 的简化增强版)。
13.3.4 轻量级状态库对比与选型
| 特性 | Zustand | Jotai | Recoil |
|------|---------|-------|--------|
| 设计理念 | 单一 store(可分拆) | 原子化状态 | 原子化状态 |
| 是否需要 Provider | 否 | 否(可选) | 是(RecoilRoot) |
| 学习曲线 | 极低 | 中等 | 中等 |
| 体积 | ~1KB | ~3KB | 较大 |
| 细粒度更新 | 通过选择器 | 自动按原子依赖 | 自动按原子/选择器依赖 |
| 异步支持 | 原生写法 | 异步原子 + Suspense | 异步选择器 + Suspense |
| 社区活跃度 | 高 | 高 | 较低(实验性) |
选型建议
- 追求极简:选 Zustand。写起来和
useState一样自然,零样板代码,体积忽略不计,是快速开发的利器。 - 需要极致渲染性能:选 Jotai。原子化让状态依赖更清晰,避免大范围重渲染,适合复杂交互和大型列表。
- Recoil 实验项目或已有技术栈:如果团队已经使用 Recoil 且稳定,可以继续;新项目如果不需其特定功能,建议优先考虑 Zustand 或 Jotai。
- 混合使用:这三个库并不互斥,你可以同时使用 Zustand 管理全局业务状态,用 Jotai 管理某些局部但需要共享的 UI 状态,完全可行。
轻量级状态库的核心价值在于“刚刚好”——提供足够的结构来组织状态,又不强加过多的概念和规则,让开发者保持高效的开发节奏。