服务端5月28日 01:18
Zustand 与 Redux 相比有哪些优缺点?### Zustand 的核心优势
**极简 API,告别样板代码**
Zustand 创建 store 只需一个函数调用,无需定义 action types、reducers、action creators。与 Redux Toolkit 的 createSlice 相比,代码量减少 60% 以上:
```js
// Zustand — 一个函数搞定 store
import { create } from 'zustand'
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
// 组件中直接用 hook
function Counter() {
const { count, increment } = useStore()
return <button onClick={increment}>{count}</button>
}
```
```js
// Redux Toolkit — 需要更多概念
import { createSlice, configureStore } from '@reduxjs/toolkit'
import { Provider, useSelector, useDispatch } from 'react-redux'
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1 },
decrement: (state) => { state.value -= 1 },
},
})
const store = configureStore({ reducer: { counter: counterSlice.reducer } })
// 还需要 Provider 包裹 + useSelector + useDispatch
function Counter() {
const count = useSelector((s) => s.counter.value)
const dispatch = useDispatch()
return <button onClick={() => dispatch(increment())}>{count}</button>
}
```
**无需 Provider,更干净的组件树**
Redux 必须在应用顶层包裹 `<Provider store={store}>`,导致组件树多出一层嵌套,测试时也需要用 `<Provider>` 包裹。Zustand 直接在模块作用域创建 store,组件通过 hook 消费状态,无需任何包裹组件。这对存量项目集成尤其友好——不用改已有的组件树结构就能接入状态管理。
**更小的体积,更快的加载**
| 库 | Gzipped 大小 |
|---|---|
| Zustand | ~1.1 KB |
| Redux Toolkit | ~8.2 KB |
| Redux Toolkit + React-Redux | ~11.8 KB |
在 3G 网络下,11 KB 的差距可带来 50-100ms 的交互时间优化。对包体积有严格限制的移动端 H5 场景,Zustand 优势明显。
**内置选择器,精准控制重渲染**
Zustand 支持细粒度订阅,只订阅需要的状态切片,自动跳过无关更新:
```js
// 只订阅 count,其他状态变化不会触发重渲染
const count = useStore((state) => state.count)
// 也支持 shallow 比较对象
import { shallow } from 'zustand/shallow'
const { count, name } = useStore(
(state) => ({ count: state.count, name: state.name }),
shallow
)
```
Redux 的 `useSelector` 同样支持选择器,但默认使用 `===` 严格比较,返回对象时需要手动使用 `shallowEqual`,容易遗忘导致不必要的重渲染。
**TypeScript 开箱即用**
Zustand 的 store 定义自带类型推导,无需额外声明类型:
```ts
const useStore = create<{ count: number; increment: () => void }>((set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
}))
// count 和 increment 类型自动推导,无需泛型
```
Redux Toolkit 虽然也有良好的 TS 支持,但 createSlice 和 configureStore 需要更多类型标注和泛型配置。
**异步操作更直观**
Zustand 处理异步不需要额外中间件,直接写 async 函数:
```js
const useStore = create((set) => ({
data: null,
loading: false,
fetchData: async () => {
set({ loading: true })
const res = await fetch('/api/data')
const data = await res.json()
set({ data, loading: false })
},
}))
```
Redux Toolkit 通过 createAsyncThunk 处理异步,需要定义 pending/fulfilled/rejected 三种状态,样板代码更多。
### Zustand 的不足
**生态和中间件相对薄弱**
Redux 拥有 Redux Saga、Redux Observable、Redux Persist 等成熟中间件生态,处理竞态、取消、重试等复杂副作用有成熟方案。Zustand 自带 persist、devtools、immer 等中间件,覆盖常见需求,但社区中间件数量和成熟度仍远不及 Redux。遇到复杂副作用场景时,往往需要自己实现或组合多个中间件。
**调试体验有差距**
Zustand 可通过 `devtools` 中间件接入 Redux DevTools,但时间旅行调试和 action 回放功能不如原生 Redux 完善。对于需要严格追踪每次状态变更、回溯 bug 的场景,Redux 的调试体验更可靠。
**大型项目实践尚在积累**
Zustand 已被 React Three Fiber、shadcn/ui、Next.js 示例等项目广泛采用,2026 年 npm 周下载量达 700 万次,但在超大型企业级应用中的最佳实践仍不如 Redux 成熟。缺少千人团队的治理模式参考和大规模重构案例。
**灵活性的双刃剑**
Zustand 不强制状态更新模式,不同开发者可能写出风格迥异的 store。在缺乏 Code Review 约束的团队中,灵活反而变成混乱。Redux 的严格单向数据流天然约束了风格统一性,新人接手代码时理解成本更低。
### 什么时候选 Zustand
- **中小型项目**:状态逻辑不复杂,追求开发速度和简洁性
- **存量项目集成**:不想引入 Provider 改动组件树,零侵入接入
- **性能敏感场景**:对包体积和重渲染有严格要求,如移动端 H5
- **快速原型开发**:重视迭代速度而非架构完备性
- **React Three Fiber / 可视化项目**:Zustand 是 R3F 生态的默认选择
### 什么时候选 Redux
- **大型企业级应用**:团队 5 人以上,需要标准化流程和严格约束
- **复杂副作用**:需要 Saga 级别的竞态处理、取消和重试能力
- **重度调试需求**:时间旅行调试对排查线上问题至关重要
- **团队已熟悉 Redux**:迁移成本高于收益,不必为了换而换
- **金融 / 交易类系统**:需要追踪每一次状态变更的审计场景
### 怎么选:一句结论
项目小、求快、求轻选 Zustand;项目大、求稳、求规范选 Redux。两者并非互斥——复杂项目中可以在全局状态用 Redux、局部状态用 Zustand,按需搭配。标签
Redux
Redux 是一个流行的 JavaScript 状态管理库,主要用于管理复杂应用的状态。它由 Dan Abramov 和 Andrew Clark 创建,并受到了 Flux 架构的启发。Redux 的核心理念是维护一个单一的全局状态对象,所有的状态变更都通过一种叫做“action”的方式来描述,然后这些 action 会通过“reducer”函数来更新状态。

前端5月27日 23:31
MobX 和 Redux 有什么区别?MobX 和 Redux 有什么区别?面试中三句话说清楚:MobX 是响应式自动追踪,改了数据视图自动更新;Redux 是函数式单向数据流,必须 dispatch action 才能改状态。MobX 写得少但调试难预测,Redux 写得多但状态可追溯。选哪个看团队——要快用 MobX,要严用 Redux。
## 核心区别
| 维度 | MobX | Redux |
|------|------|-------|
| 编程范式 | 响应式 + 面向对象 | 函数式 + 单向数据流 |
| 状态修改 | 直接赋值,自动追踪 | dispatch action → reducer 返回新状态 |
| 样板代码 | 极少 | 较多(即使 RTK 也比 MobX 多) |
| 状态结构 | 嵌套对象随意写 | 推荐扁平化 + normalize |
| 时间旅行 | 有限支持 | Redux DevTools 完整支持 |
| 学习曲线 | 入门快,精通需理解响应式原理 | 入门慢,但模式固定好掌握 |
| TypeScript | 良好 | 良好(RTK 出厂即支持) |
## 代码对比:同一个 Todo
### MobX 写法
```javascript
import { makeAutoObservable, computed } from "mobx";
class TodoStore {
todos = [];
filter = "all";
constructor() {
makeAutoObservable(this);
}
get filteredTodos() {
if (this.filter === "completed") return this.todos.filter((t) => t.done);
if (this.filter === "active") return this.todos.filter((t) => !t.done);
return this.todos;
}
addTodo(text) {
this.todos.push({ id: Date.now(), text, done: false });
}
toggle(id) {
const todo = this.todos.find((t) => t.id === id);
if (todo) todo.done = !todo.done;
}
}
```
直接改属性,MobX 内部的依赖追踪机制会自动触发对应组件重渲染。这就是响应式的核心——你写的是普通赋值,背后 MobX 帮你做了订阅和通知。
### Redux Toolkit 写法
2026 年 Redux 官方推荐用 Redux Toolkit(RTK),不再用 `createStore` 那套手写模板。
```javascript
import { createSlice, configureStore, createSelector } from "@reduxjs/toolkit";
const todoSlice = createSlice({
name: "todos",
initialState: { items: [], filter: "all" },
reducers: {
addTodo: (state, action) => {
state.items.push({ id: Date.now(), text: action.payload, done: false });
},
toggle: (state, action) => {
const todo = state.items.find((t) => t.id === action.payload);
if (todo) todo.done = !todo.done;
},
setFilter: (state, action) => {
state.filter = action.payload;
},
},
});
export const { addTodo, toggle, setFilter } = todoSlice.actions;
const store = configureStore({ reducer: { todos: todoSlice.reducer } });
// Selector(带 memo)
const selectFiltered = createSelector(
[(s) => s.todos.items, (s) => s.todos.filter],
(items, filter) => {
if (filter === "completed") return items.filter((t) => t.done);
if (filter === "active") return items.filter((t) => !t.done);
return items;
}
);
```
RTK 内置了 Immer,所以在 reducer 里可以直接修改state(实际产出的是不可变新对象)。这大大减少了 Redux 的样板代码量。
## 面试追问:MobX 的响应式原理是什么?
MobX 在属性读取时收集依赖(通过 Proxy 或 getter 劫持),在属性写入时通知所有观察者。组件渲染时读取 observable 属性,MobX 记录这个组件依赖这些属性;属性变化时,MobX 精确触发对应组件重渲染。所以 MobX 不需要手动 `shouldComponentUpdate` 或 `React.memo`,它天然做到了最小化更新。代价是调试时不容易追踪谁改了这个值,因为赋值点分散在代码各处。
## 面试追问:为什么 Redux 要求状态不可变?
两个原因。第一,不可变让引用比较成为可能——`oldState !== newState` 就知道状态变了,不用深比较,这是 Redux 性能模型的基础。第二,不可变保证了时间旅行调试——每次状态变更都产生新的快照,可以回退到任意历史节点。如果直接修改原对象,历史状态会被覆盖,DevTools 的时间旅行就废了。这也是 MobX 时间旅行支持有限的根本原因。
## 性能:谁更快?
2026 年基准测试数据:
| 操作 | MobX | Redux Toolkit |
|------|------|---------------|
| 简单更新 | 0.3ms | 0.8ms |
| 嵌套更新 | 0.4ms | 1.2ms |
| 内存占用 | 3.1MB | 4.2MB |
MobX 快在哪?它自动追踪依赖,只更新真正受影响的组件。Redux 每次 dispatch 后要过一遍 `useSelector` 的比较逻辑,组件需要自己决定要不要重渲染。当然,Redux 配合 `reselect` 做 memo 化后差距会缩小,但这是需要开发者手动做的。
## 怎么选?
**选 MobX:** 小团队快速迭代、状态嵌套深(比如树形编辑器)、团队 OOP 背景强、不想写样板代码。
**选 Redux (RTK):** 大型项目多人协作、需要严格的代码规范和可追溯的状态变更、需要 DevTools 时间旅行、团队函数式偏好。
**都不选?** 2026 年 Zustand(2.1KB)因为极简 API 和零样板代码,成为很多新项目的默认选择。它没有 MobX 的响应式黑盒,也没有 Redux 的模板负担。如果你的项目状态管理不复杂,Zustand 值得一看。
## 一句话总结
MobX 用魔法帮你省事,Redux 用规矩帮你兜底。面试答区别,先说范式(响应式 vs 函数式),再说可变性(可变 vs 不可变),最后说取舍(灵活 vs 可预测)。前端5月27日 18:11
MobX 和 Redux 有什么区别,应该如何选择?MobX 和 Redux 是前端领域两种主流的状态管理方案,但它们的底层思路完全不同。Redux 走的是函数式、显式派发的路线,MobX 走的是响应式、自动追踪的路线。理解这个根本分歧,是做出技术选型的前提。
## 核心设计理念的区别
Redux 的哲学可以用三句话概括:单一数据源、状态只读、纯函数修改。整个应用的状态存在一棵对象树里,你永远不能直接改它,只能通过 dispatch 一个 action,由 reducer 这个纯函数算出下一个状态。这种设计让状态变化变得可追踪、可复现。
MobX 的思路则截然相反——它允许你直接修改状态,但通过 observable 包装后,MobX 会自动追踪哪些组件依赖了哪些数据,当数据变化时自动触发对应组件的更新。你不需要写 reducer,不需要 dispatch action,改了数据,UI 就会跟着变。
用代码对比会更直观。同样是管理一个计数器:
**Redux 写法:**
```js
// 定义 action type
const INCREMENT = 'counter/increment';
// 定义 action creator
function increment() {
return { type: INCREMENT };
}
// 定义 reducer
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case INCREMENT:
return { ...state, value: state.value + 1 };
default:
return state;
}
}
// 组件中使用
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return <button onClick={() => dispatch(increment())}>{count}</button>;
}
```
**MobX 写法:**
```js
// 定义 store
class CounterStore {
@observable value = 0;
@action
increment() {
this.value++;
}
}
const counterStore = new CounterStore();
// 组件中使用(observer 自动追踪依赖)
const Counter = observer(() => (
<button onClick={() => counterStore.increment()}>
{counterStore.value}
</button>
));
```
可以看到,Redux 为了改一个数字,需要定义 action type、action creator、reducer 三层结构;而 MobX 直接在方法里改值就行。这就是两种方案最核心的体验差异。
## 样板代码与开发效率
Redux 最常被诟病的就是样板代码太多。一个简单的增删改查,你可能要写 action types、action creators、reducers、selectors,还要配置 store 和 middleware。对于小需求来说,这套流程显得很重。
Redux Toolkit 的出现大幅缓解了这个问题。它用 `createSlice` 把 action 和 reducer 合并到一起,用 `createAsyncThunk` 处理异步,样板代码减少了很多:
```js
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => { state.value += 1; },
},
});
```
注意上面 reducer 里直接写了 `state.value += 1`——Redux Toolkit 内部用了 Immer,所以你表面上在"直接修改",实际上 Immer 帮你生成了不可变的新状态。
相比之下,MobX 从一开始就不需要这么多仪式感。定义 observable 数据、写 action 方法、用 observer 包组件,这三步就够了。对于快速迭代的项目,这种简洁性能省下不少时间。
## 性能机制对比
Redux 的更新机制比较"粗暴"——每次 dispatch action 后,reducer 返回新的 state 对象,所有通过 `useSelector` 订阅了这个 state 的组件都会被通知。虽然 `useSelector` 默认用浅比较来决定是否重渲染,但如果 selector 返回的引用每次都不同(比如返回了一个新数组或新对象),组件就会不必要地更新。这时候需要用 `reselect` 来做记忆化:
```js
const selectFilteredItems = createSelector(
state => state.items,
state => state.filter,
(items, filter) => items.filter(item => item.type === filter)
);
```
MobX 的机制更精细。它在运行时追踪每个 observer 组件实际访问了哪些 observable 属性,只有这些属性变化时才会重渲染组件。这个过程是自动的,不需要开发者手动优化。如果一个组件只用了 `store.userName`,那 `store.age` 变化不会触发它更新。
在大多数中大型应用中,这种细粒度追踪比 Redux 的浅比较更高效,尤其是状态树很大但单个组件只用其中一小部分数据的场景。
## 异步处理的差异
Redux 处理异步的经典方案是中间件。早期用 redux-thunk,后来社区转向 redux-saga 和 Redux Toolkit 的 `createAsyncThunk`:
```js
const fetchUser = createAsyncThunk('user/fetch', async (userId) => {
const res = await fetch(`/api/users/${userId}`);
return res.json();
});
const userSlice = createSlice({
name: 'user',
initialState: { data: null, status: 'idle' },
extraReducers: builder => {
builder
.addCase(fetchUser.pending, state => { state.status = 'loading'; })
.addCase(fetchUser.fulfilled, (state, action) => {
state.status = 'succeeded';
state.data = action.payload;
})
.addCase(fetchUser.rejected, state => { state.status = 'failed'; });
},
});
```
MobX 处理异步则简单得多——在 action 里直接写 async/await 就行,不需要额外的中间件:
```js
class UserStore {
@observable data = null;
@observable status = 'idle';
@action
async fetchUser(userId) {
this.status = 'loading';
try {
const res = await fetch(`/api/users/${userId}`);
this.data = await res.json();
this.status = 'succeeded';
} catch {
this.status = 'failed';
}
}
}
```
如果你用过 Redux 处理复杂异步流程(比如并行请求、取消请求、条件触发),就会感受到 Redux 在这方面的抽象能力更强,但也更啰嗦。MobX 胜在直觉和简洁。
## TypeScript 体验
Redux 和 TypeScript 的结合曾经很痛苦——你需要为 state、action、dispatch 分别定义类型,类型体操写起来很繁琐。Redux Toolkit 改善了很多,`createSlice` 能自动推断出 action 的类型,但遇到 `extraReducers` 和异步 thunk 时,类型推导仍然需要额外的泛型标注。
MobX 和 TypeScript 的配合更顺畅。observable 属性的类型就是你在 class 里声明的类型,action 的参数类型也是方法签名的类型,不需要额外的类型桥接。编译器能自然地推断出大多数类型,代码也更易读。
## 调试与可维护性
Redux 最大的优势之一是调试体验。因为每次状态变化都对应一个明确的 action,Redux DevTools 能完整记录状态变更历史,支持时间旅行——你可以回到任意一个历史状态查看应用当时的样子。这对排查复杂 bug 非常有用。
MobX 的调试相对困难一些。虽然 MobX 也有 DevTools,但由于状态可以直接在 action 中修改,修改点分散在各处,不像 Redux 那样有一个统一的 action 日志。在大型项目里,如果不严格遵守"所有状态修改都在 action 中进行"的规范,调试起来会很头疼。
可维护性方面,Redux 的约束实际上是一种保护——它强制团队按照统一的模式写代码,新人接手时只要理解了 Redux 的数据流模式,就能比较容易地看懂业务逻辑。MobX 的自由度更高,但如果团队没有建立好编码规范,代码风格可能千差万别。
## 什么时候选 Redux
- 团队规模较大,需要统一的状态管理模式来降低协作成本
- 业务逻辑对状态变化的可追溯性要求高(如金融、交易系统)
- 已有成熟的 Redux 技术栈和工具链,团队经验丰富
- 需要频繁使用 DevTools 的时间旅行功能排查问题
- 项目中大量使用中间件处理复杂副作用(如轮询、WebSocket)
## 什么时候选 MobX
- 团队规模中小型,追求开发速度和迭代效率
- 状态模型比较复杂(深嵌套对象、频繁修改),不可变更新写起来很痛苦
- 希望减少样板代码,把精力集中在业务逻辑上
- 已有面向对象编程背景的团队,class + decorator 的写法更自然
## 两者能否共存
在同一个项目里混用 Redux 和 MobX 并不常见,但也不是不可行。比如你可以在全局状态层面使用 Redux(保证可追溯性),在局部复杂表单状态使用 MobX(减少不可变更新的负担)。不过这种方式会增加新人理解项目的成本,除非有非常充分的理由,否则不建议这么做。
更值得考虑的替代方案是 Zustand、Jotai 或 Valtio 这些轻量级状态库。Zustand 的 API 比 Redux 简洁得多,同时保留了不可变状态的核心思想;Valtio 则融合了 MobX 的可变状态思路和 Proxy 追踪机制,体积更小。如果你的项目不需要 Redux 的完整工具链,这些轻量方案值得认真评估。前端2024年8月5日 12:48
Redux 如何实现自定义中间件在Redux中,中间件是一种强大的机制,允许开发者在action被发送到reducer之前插入自己的逻辑。创建自定义的Redux中间件涉及到编写一个函数,该函数按照Redux中间件API的规格返回一个满足特定签名的函数。
我将向您展示如何自定义实现一个简单的日志中间件,该中间件的作用是在action被派发时在控制台输出日志信息。
以下是自定义Redux中间件的基本步骤:
1. 编写一个函数,该函数接收`store`的`dispatch`和`getState`方法。
2. 该函数返回一个接收下一个中间件的`next`函数的函数。
3. 返回的函数再返回一个接收action的函数。
4. 在最内层的函数体内,可以执行自定义的逻辑,然后调用`next(action)`将action传递给链中的下一个中间件或reducer。
下面是一个自定义日志中间件的例子:
```javascript
// 自定义日志中间件
const loggerMiddleware = store => next => action => {
// 自定义的逻辑:在当前action被处理之前输出日志
console.log('dispatching', action);
// 调用链中的下一个中间件或reducer
let result = next(action);
// 自定义的逻辑:在action被处理后输出新的状态
console.log('next state', store.getState());
// 返回result,因为middleware的链需要从next(action)获取返回值
return result;
};
export default loggerMiddleware;
```
在上述的中间件代码中:
- `store`: Redux store实例,它包含了`dispatch`和`getState`方法。
- `next`: 是一个将action传递给链中下一个处理者(中间件或reducer)的函数。
- `action`: 是当前正在处理的action对象。
使用这个中间件的典型方式是在创建Redux store时应用它:
```javascript
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers';
import loggerMiddleware from './middleware/loggerMiddleware';
// 使用applyMiddleware来增强store,添加自定义的loggerMiddleware
const store = createStore(
rootReducer,
applyMiddleware(loggerMiddleware)
);
export default store;
```
在这个例子中,任何派发到store的action都会先经过`loggerMiddleware`这个中间件,在控制台输出action信息,然后继续沿中间件链传递,直到最终被reducer处理。
这只是自定义中间件的一个简单例子,但您可以根据需要在中间件中实现更复杂的逻辑,例如异步操作、路由导航或其他您想要的任何自定义行为。