Redux 和 Zustand对比及二者可序列化相关问题
作者:shizhishi |
发布时间: |
分类:前端进阶 |
声明:个人技术博客 • 非官方 • 仅供技术学习交流
**Redux 和 Zustand 的核心差异之一在于对“可序列化数据”的要求,尤其是 Redux 明确不建议存储函数,而 Zustand 更加灵活**。 下面我们从 **设计理念、可序列化性、函数存储、适用场景** 等多个维度进行系统对比,并重点解释“可序列化”和“为什么 Redux 不该存函数”。 指可以被 `JS...
**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(`window.__INITIAL_STATE__ = ${JSON.stringify(store.getState())}`);
// 客户端
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();
+1
```
#### 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 |