Redux 和 Zustand 的核心差异之一在于对 「可序列化数据」 的要求,尤其是 Redux 明确不建议存储函数,而 Zustand 更加灵活。
下面我们从 设计理念、可序列化性、函数存储、适用场景 等多个维度进行系统对比,并重点解释 「可序列化」 和 「为什么 Redux 不该存函数」 。
一、核心对比概览
| 特性 | Redux | Zustand |
|---|---|---|
| 设计理念 | 单一可信源 + 严格的不可变性 + 可预测性 | 轻量、简单、灵活、函数式 |
| 状态存储 | Store 是纯对象 (推荐可序列化) | Store 可以是任意类型 (包括函数) |
| 是否支持存储函数 | 不推荐 (违反可序列化) | 支持 |
| 中间件生态 | 丰富 (Redux Thunk, Saga, DevTools) | 简单中间件支持 |
| 调试工具 | Redux DevTools(时间旅行调试) | Zustand DevTools(类似) |
| 学习成本 | 较高 (action, reducer, store, middleware) | 极低 (一个 create 函数) |
| 包体积 | 较大 (需配合 toolkit) | 极小 (~1.5KB) |
二、什么是 「可序列化」(Serializable)?
可序列化数据:
指可以被 JSON.stringify() 正确转换的数据类型,主要包括:
// 可序列化
{
number: 42,
string: "hello",
boolean: true,
null: null,
array: [1, 2, 3],
object: { a: 1 },
undefined → 会被忽略
}
不可序列化数据:
无法被 JSON.stringify() 正确处理:
{
function: () => {}, // 函数
symbol: Symbol('id'), // Symbol
date: new Date(), // 会被转成字符串
regex: /abc/, // 会被转成空对象
undefined: undefined, // 会被忽略
bigint: 123n, // 需特殊处理
nan: NaN, // 会变成 null
infinity: Infinity // 会变成 null
}
三、为什么 Redux 要求状态可序列化?为什么不能存函数?
这是 Redux 核心设计哲学决定的,主要原因有:
1. 支持时间旅行调试 (Time Travel Debugging)
- Redux DevTools 可以记录每一步
action,并让你 「回退」 到任意状态。 - 如果状态中包含函数,回退后函数的闭包环境可能已改变,导致行为不一致。
// 错误示例:状态中存函数
const store = createStore(() => ({
actions: {
increment: () => dispatch({ type: 'INC' })
}
}))
回退后,
increment函数引用的dispatch可能已失效。
2. 服务端渲染 (SSR) 和状态 hydration
- SSR 时,服务器将状态序列化为 JSON 字符串,发送给客户端。
- 客户端再反序列化 (
JSON.parse()) 恢复状态。 - 函数无法被序列化,会丢失。
// 服务器
res.send(`<script>window.__INITIAL_STATE__ = ${JSON.stringify(store.getState())}</script>`);
// 客户端
const preloadedState = window.__INITIAL_STATE__; // 函数字段会丢失!
3. 持久化 (如 localStorage)
- 使用
redux-persist将状态存入localStorage。 localStorage只支持字符串,必须JSON.stringify()。- 函数无法存储。
4. 可预测性和可测试性
- Redux 希望状态是 「纯数据」,reducer 是纯函数。
- 函数是 「行为」,混入状态会破坏数据与逻辑的分离。
四、 Redux 中如何处理 「函数逻辑」?
虽然不能把函数存进 state,但 Redux 提供了其他方式:
1. Action Creators(函数)
export const increment = () => ({ type: 'INCREMENT' });
2. Redux Thunk / Redux Saga
用于处理异步和副作用:
// Thunk
const fetchUser = (id) => async (dispatch, getState) => {
const res = await api.getUser(id);
dispatch({ type: 'USER_RECEIVED', payload: res.data });
};
3. Selector(用 Reselect 缓存计算)
import { createSelector } from 'reselect';
const selectItems = state => state.items;
const selectTax = state => state.tax;
export const selectTotal = createSelector(
[selectItems, selectTax],
(items, tax) => items.reduce(...) * (1 + tax)
);
总结:逻辑放外面,状态只存数据。
五、 Zustand 为什么可以存储函数?
Zustand 的设计更现代、更灵活,它不强制要求状态可序列化。
1. Zustand 允许在 store 中直接定义函数
import { create } from 'zustand';
const useStore = create((set, get) => ({
count: 0,
// 直接定义函数
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
// 甚至可以存外部函数
logger: console.log,
}));
2. 使用方式更简洁
const { count, increment } = useStore();
<button onClick={increment}>+1</button>
3. 也支持 actions 分离 (推荐)
const useStore = create((set) => ({
count: 0,
setCount: (count) => set({ count }),
}));
// 使用
const { count, setCount } = useStore();
六、 Zustand 的序列化问题
虽然 Zustand 允许存函数,但如果你需要:
- 持久化到 localStorage
- SSR hydration
- 时间旅行调试
你就需要手动处理函数的序列化问题。
解决方案:使用中间件 persist
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useStore = create(persist(
(set, get) => ({
count: 0,
name: 'John',
// 函数不会被持久化
increment: () => set({ count: get().count + 1 }),
}),
{
name: 'my-store', // 存储名
// 自定义序列化 (可选)
serialize: (state) => JSON.stringify({ state: state.state }),
deserialize: (str) => ({ state: JSON.parse(str).state })
}
));
persist中间件会自动忽略函数,只持久化可序列化数据。
七、如何选择?Redux vs Zustand
| 场景 | 推荐 |
|---|---|
| 大型复杂应用,需要严格状态管理、时间旅行、 SSR | Redux + RTK |
| 中小型项目,追求开发效率、轻量 | Zustand |
| 需要持久化、 SSR | Redux 更成熟,Zustand 需配置 |
| 团队协作,规范性强 | Redux(约束多,不易出错) |
| 快速原型开发 | Zustand(几行代码搞定) |
总结
| 问题 | 回答 |
|---|---|
| Redux 为什么不能存函数? | 为了支持可序列化:时间旅行、 SSR 、持久化、可预测性 |
| 什么是可序列化? | 能被 JSON.stringify 正确处理的数据 (纯数据) |
| Zustand 为什么能存函数? | 设计更灵活,不强制序列化,适合现代轻量应用 |
| Zustand 能持久化吗? | 可以,但函数不会被保存,需用 persist 中间件 |
| 我该用哪个? | 复杂项目用 Redux,简单项目用 Zustand |

Comments NOTHING