前言
在 React 生态中,状态管理一直是前端开发的核心议题。从 Redux 的"样板代码地狱"到 MobX 的响应式编程,再到 Context API 的原生方案,状态管理的演进始终围绕一个核心问题:如何在保证可维护性的同时,最小化不必要的组件重渲染?
2024-2025 年,新一代状态管理库以"轻量、直观、高性能"的姿态崛起。Zustand(npm 周下载量突破 500 万)、Jotai(原子化状态的代表)和 Preact Signals(细粒度响应式)分别代表了三种不同的设计哲学。本文将从设计理念、API 风格、性能表现和实战应用四个维度,对这三个库进行深度对比。
一、Zustand:极简主义的 Flux 方案
1.1 设计理念
Zustand 由 React Spring 团队开发,核心理念是"用最少的 API 做最多的事"。它保留了 Flux 的单向数据流思想,但去掉了 Redux 中繁琐的 action、reducer、dispatch 样板代码。
import { create } from 'zustand';
interface TodoState {
todos: { id: number; text: string; done: boolean }[];
filter: 'all' | 'active' | 'done';
addTodo: (text: string) => void;
toggleTodo: (id: number) => void;
setFilter: (filter: 'all' | 'active' | 'done') => void;
}
const useStore = create<TodoState>((set) => ({
todos: [],
filter: 'all',
addTodo: (text) =>
set((state) => ({
todos: [...state.todos, { id: Date.now(), text, done: false }],
})),
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((t) =>
t.id === id ? { ...t, done: !t.done } : t
),
})),
setFilter: (filter) => set({ filter }),
}));
1.2 核心特性
选择性订阅是 Zustand 最大的性能优化手段。通过 selector 函数,组件只在自己关心的状态片段变化时重渲染:
// 只有 filter 变化时才会重渲染 const filter = useStore((state) => state.filter); // 只有 active todo 数量变化时才会重渲染 const activeCount = useStore( (state) => state.todos.filter((t) => !t.done).length );
中间件生态丰富,官方提供了 devtools、persist、immer、subscribeWithSelector 等中间件:
import { persist } from 'zustand/middleware';
const useStore = create(
persist<TodoState>(
(set) => ({ /* ... */ }),
{ name: 'todo-storage' } // 自动持久化到 localStorage
)
);
1.3 适用场景
-
中小型项目快速开发
-
需要从 Redux 迁移但不想重写全部逻辑
-
需要持久化、时间旅行调试等开箱即用功能
二、Jotai:原子化状态的优雅实践
2.1 设计理念
Jotai 的灵感来自 Recoil,采用原子化(Atomic)状态模型。每个 atom 是独立的最小状态单元,组件通过 useAtom 订阅特定的 atom,实现天然的细粒度更新。
import { atom, useAtom } from 'jotai';
const todosAtom = atom<{ id: number; text: string; done: boolean }[]>([]);
const filterAtom = atom<'all' | 'active' | 'done'>('all');
// 派生 atom — 自动追踪依赖
const filteredTodosAtom = atom((get) => {
const todos = get(todosAtom);
const filter = get(filterAtom);
switch (filter) {
case 'active': return todos.filter((t) => !t.done);
case 'done': return todos.filter((t) => t.done);
default: return todos;
}
});
2.2 核心特性
派生 atom 是 Jotai 最强大的功能。它自动追踪 get 调用的依赖,当任何依赖 atom 变化时,派生 atom 自动重新计算:
// 统计 atom
const statsAtom = atom((get) => {
const todos = get(todosAtom);
return {
total: todos.length,
active: todos.filter((t) => !t.done).length,
done: todos.filter((t) => t.done).length,
};
});
function Stats() {
const [stats] = useAtom(statsAtom);
return <span>总计: {stats.total} | 待完成: {stats.active}</span>;
}
atomFamily 处理动态列表场景,为每个实体创建独立的 atom:
import { atomFamily } from 'jotai/utils';
const todoAtomFamily = atomFamily((id: number) =>
atom({ id, text: '', done: false })
);
// 每个 TodoItem 只订阅自己的 atom
function TodoItem({ id }: { id: number }) {
const [todo, setTodo] = useAtom(todoAtomFamily(id));
// ...
}
2.3 适用场景
-
状态之间存在复杂的派生/计算关系
-
需要极致的渲染优化(每个 atom 独立触发更新)
-
大型应用中状态模块需要独立维护
三、Preact Signals:细粒度响应式的降维打击
3.1 设计理念
Signals 模型由 Solid.js 首创,Preact Signals 将其引入 React 生态。核心理念是"信号自动追踪依赖,变化时精确更新 DOM,跳过 React 渲染管线"。
import { signal, computed } from '@preact/signals-react';
const todos = signal<{ id: number; text: string; done: boolean }[]>([]);
const filter = signal<'all' | 'active' | 'done'>('all');
// computed 自动追踪 signal 依赖
const filteredTodos = computed(() => {
const list = todos.value;
const f = filter.value;
switch (f) {
case 'active': return list.filter((t) => !t.done);
case 'done': return list.filter((t) => t.done);
default: return list;
}
});
3.2 核心特性
绕过 React 渲染是 Signals 最大的性能优势。当在 JSX 中直接使用 signal 时,信号变化会直接更新 DOM 节点,不触发组件重新渲染:
function TodoCount() {
// count 变化时,只有 <span> 的文本更新
// 整个 TodoCount 组件不会重新渲染
const count = computed(() => todos.value.filter((t) => !t.done).length);
return <span>待完成: {count}</span>;
}
与 React 无缝集成:Preact Signals 提供了 React 兼容层,可以在现有 React 项目中直接使用:
import { useSignal } from '@preact/signals-react';
function Counter() {
const count = useSignal(0);
return (
<button onClick={() => count.value++}>
点击了 {count} 次 {/* 直接渲染 signal,自动追踪 */}
</button>
);
}
3.3 适用场景
-
高频更新场景(动画、实时数据流、游戏 UI)
-
对渲染性能有极致要求的应用
-
希望减少 React 渲染开销但不想重构架构
四、三者横向对比
| 维度 | Zustand | Jotai | Preact Signals |
|---|---|---|---|
| API 风格 | Store + Selector | Atom + useAtom | Signal + .value |
| 学习曲线 | 低(类 Redux) | 中(理解原子化) | 低(直觉式) |
| 渲染优化 | Selector 订阅 | 原子粒度 | 信号级(绕过 React) |
| TypeScript | 优秀 | 优秀 | 优秀 |
| 持久化 | 内置中间件 | 需第三方 | 无内置 |
| DevTools | Redux DevTools | Jotai DevTools | 有限 |
| 包体积 | ~1KB | ~3KB | ~2KB |
| 生态成熟度 | ★★★★★ | ★★★★ | ★★★ |
| 社区活跃度 | 极高 | 高 | 中 |
五、实战:同一功能三种实现
以下用同一个 Todo 应用(添加、切换、筛选、统计)展示三种方案的代码差异。
5.1 Zustand 实现
import { create } from 'zustand';
interface Todo { id: number; text: string; done: boolean }
interface Store {
todos: Todo[];
filter: 'all' | 'active' | 'done';
addTodo: (text: string) => void;
toggleTodo: (id: number) => void;
setFilter: (f: 'all' | 'active' | 'done') => void;
getFiltered: () => Todo[];
getStats: () => { total: number; active: number; done: number };
}
const useStore = create<Store>((set, get) => ({
todos: [],
filter: 'all',
addTodo: (text) =>
set((s) => ({ todos: [...s.todos, { id: Date.now(), text, done: false }] })),
toggleTodo: (id) =>
set((s) => ({ todos: s.todos.map((t) => t.id === id ? { ...t, done: !t.done } : t) })),
setFilter: (filter) => set({ filter }),
getFiltered: () => {
const { todos, filter } = get();
return filter === 'all' ? todos : todos.filter((t) => (filter === 'active' ? !t.done : t.done));
},
getStats: () => {
const { todos } = get();
return { total: todos.length, active: todos.filter((t) => !t.done).length, done: todos.filter((t) => t.done).length };
},
}));
5.2 Jotai 实现
import { atom } from 'jotai';
interface Todo { id: number; text: string; done: boolean }
const todosAtom = atom<Todo[]>([]);
const filterAtom = atom<'all' | 'active' | 'done'>('all');
const addTodoAtom = atom(null, (get, set, text: string) => {
const todos = get(todosAtom);
set(todosAtom, [...todos, { id: Date.now(), text, done: false }]);
});
const toggleTodoAtom = atom(null, (get, set, id: number) => {
set(todosAtom, get(todosAtom).map((t) => t.id === id ? { ...t, done: !t.done } : t));
});
const filteredTodosAtom = atom((get) => {
const todos = get(todosAtom);
const filter = get(filterAtom);
return filter === 'all' ? todos : todos.filter((t) => (filter === 'active' ? !t.done : t.done));
});
const statsAtom = atom((get) => {
const todos = get(todosAtom);
return { total: todos.length, active: todos.filter((t) => !t.done).length, done: todos.filter((t) => t.done).length };
});
5.3 Preact Signals 实现
import { signal, computed } from '@preact/signals-react';
interface Todo { id: number; text: string; done: boolean }
const todos = signal<Todo[]>([]);
const filter = signal<'all' | 'active' | 'done'>('all');
const addTodo = (text: string) => {
todos.value = [...todos.value, { id: Date.now(), text, done: false }];
};
const toggleTodo = (id: number) => {
todos.value = todos.value.map((t) => t.id === id ? { ...t, done: !t.done } : t);
};
const filteredTodos = computed(() => {
const f = filter.value;
return f === 'all' ? todos.value : todos.value.filter((t) => (f === 'active' ? !t.done : t.done));
});
const stats = computed(() => ({
total: todos.value.length,
active: todos.value.filter((t) => !t.done).length,
done: todos.value.filter((t) => t.done).length,
}));
六、选型建议
按项目规模
| 项目类型 | 推荐方案 | 理由 |
|---|---|---|
| 个人项目 / MVP | Zustand | API 最简单,5 分钟上手 |
| 中型团队项目 | Zustand / Jotai | Zustand 适合习惯 Redux 的团队;Jotai 适合追求优雅架构的团队 |
| 大型复杂应用 | Jotai | 原子化拆分天然适合模块化,派生 atom 处理复杂计算关系 |
| 高性能实时应用 | Preact Signals | 绕过 React 渲染管线,适合高频数据更新 |
按团队背景
-
Redux 老团队 → Zustand(迁移成本最低)
-
Recoil / 函数式编程爱好者 → Jotai(原子化思维一致)
-
Solid.js / Vue 转 React → Preact Signals(响应式思维一致)
总结
2025 年的前端状态管理已经告别了"唯一正确答案"的时代。Zustand 以极简 API 和成熟生态成为最稳妥的选择;Jotai 以原子化模型和派生计算提供了最优雅的架构方案;Preact Signals 以细粒度响应式带来了最极致的性能表现。
实际项目中,不必拘泥于单一方案。Zustand 管理全局状态 + Signals 处理高频局部更新,或者 Jotai 管理业务状态 + Zustand 做持久化层,都是可行的混合架构。关键是根据团队技术栈、项目规模和性能需求做出务实的选择。
推荐延伸阅读:Zustand 官方文档(zustand-demo.pmnd.rs)、Jotai 源码解析(github.com/pmndrs/jotai)、Signals 规范提案(github.com/tc39/proposal-signals)
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/qq_38133850/article/details/161106011



