标签

Redux

Redux 是一个流行的 JavaScript 状态管理库,主要用于管理复杂应用的状态。它由 Dan Abramov 和 Andrew Clark 创建,并受到了 Flux 架构的启发。Redux 的核心理念是维护一个单一的全局状态对象,所有的状态变更都通过一种叫做“action”的方式来描述,然后这些 action 会通过“reducer”函数来更新状态。

Redux
前端5月31日 15:55
MobX 和 Redux 到底该怎么选?适合哪些场景?MobX 和 Redux 的区别不只是 API 写法不同,而是状态管理哲学不同。Redux 强调显式数据流:组件 dispatch action,reducer 生成新 state,状态变化可以被记录和回放。MobX 强调响应式模型:你修改 observable,系统自动知道哪些 computed、reaction 或 observer 组件需要更新。 如果用一句话选型:需要强约束、审计和统一协作时偏 Redux;需要快速建模复杂业务对象、减少样板代码时偏 MobX。现在 Redux Toolkit 已经大幅减少模板代码,所以不能再简单说“Redux 一定啰嗦”。但 MobX 在深层对象、表单状态和局部复杂交互里仍然很顺手。 ```ts // Redux Toolkit const slice = createSlice({ name: "counter", initialState: { value: 0 }, reducers: { inc: state => { state.value += 1; } } }); // MobX class CounterStore { value = 0; constructor() { makeAutoObservable(this); } inc() { this.value += 1; } } ``` Redux Toolkit 里看起来也能“直接改 state”,但那是 Immer 帮你生成不可变结果。MobX 的直接修改则是它本身的响应式模型,依赖追踪发生在读取和写入之间。两者都能写得很现代,真正影响选择的是团队调试方式、业务复杂度和长期维护成本。 还有一个现实因素是招聘和交接成本。Redux 的资料、范式和候选人经验更多,新人即使没接触过项目,也容易顺着 action、slice、selector 找到入口。MobX 项目如果 store 设计得好,上手同样很快;如果设计得随意,新人需要先理解一套隐式依赖网络。选型时把团队未来一年的人数变化也算进去,往往比单纯比较代码量更实际。 ## 追问 ### Redux 的优势现在还明显吗? 明显,尤其是在多人协作和复杂状态审计场景里。Redux 的 action 日志、DevTools、时间旅行调试仍然很强,线上问题复盘时能看到状态如何一步步变化。取舍是你要接受更明确的流程和更多约束,哪怕 Redux Toolkit 已经减少了不少样板。金融、交易、权限流转这类系统,显式数据流带来的可追溯性通常比少写几行代码更重要。 ### MobX 更适合哪些业务? MobX 适合状态结构像业务对象一样自然变化的场景,比如复杂表单、编辑器、看板、低代码配置器和局部交互很多的后台页面。它允许你用 class 表达领域模型,用 computed 表达派生值,用 observer 自动连接 UI。边界是自由度越高,团队规范越重要。若大家随手在任意位置改 observable,又不给 action 命名,后期排查会比 Redux 更痛苦。 ### 性能上 MobX 一定比 Redux 更好吗? 不一定,但 MobX 的默认更新粒度通常更细。它追踪组件实际读取的 observable 字段,所以某个字段变化只影响真正用到它的组件。Redux 依赖 selector 和引用比较,写得好同样很快,写得差则容易因为新对象、新数组导致重复渲染。取舍在于 MobX 把优化自动化,Redux 把优化显式化;前者省心,后者更可控。 ### TypeScript 项目选哪一个更舒服? MobX 的 class 模型和 TypeScript 搭配很自然,字段、方法、getter 的类型就是业务模型本身。Redux Toolkit 的类型体验也已经比旧 Redux 好很多,`createSlice` 能推断 action 和 state,但异步 thunk、RootState、Dispatch 仍然需要一些模板。取舍是 MobX 写业务模型更顺,Redux 写团队规范更统一。大型团队里,类型舒服不一定是唯一目标,统一的数据流和工具链也很值钱。 ### 能不能在一个项目里同时用 MobX 和 Redux? 可以,但要非常克制。比如全局登录态、权限、审计相关状态放 Redux,某个复杂编辑器内部用 MobX 管局部模型,这是有边界的混用。踩坑点是没有划清职责,导致同一份数据在两个 store 里各存一份,最终同步逻辑比状态管理本身还复杂。除非收益明确,否则更建议选一个主方案,再用局部 React state 或轻量库补足边角。 如果项目并不复杂,却又觉得 Redux 和 MobX 都偏重,也可以把 Zustand、Jotai、Valtio 这类轻量方案纳入比较。Zustand API 简单,适合轻量全局状态;Jotai 更偏原子化组合;Valtio 则接近可变对象代理的体验。这里的取舍是生态、团队熟悉度和调试能力,不要只看示例代码短不短。状态管理选型最怕为了“新”而换,最后业务复杂度没降,团队学习成本反而升了。 落地时可以先画出状态的生命周期:哪些状态跨页面共享,哪些只服务某个复杂组件,哪些需要被审计或回放。跨团队、跨流程的状态更适合 Redux 这种强约束方案;局部领域模型、频繁编辑和深层对象更适合 MobX。这个判断比“哪个库更流行”靠谱,因为状态管理的问题通常不是 API 不够漂亮,而是边界没有定义清楚。 所以 MobX 和 Redux 没有绝对胜负。Redux 像一套清晰的交通规则,MobX 像更灵活的自动导航;项目越重协作和审计,越需要规则,项目越重局部复杂交互,越能体现 MobX 的效率。
服务端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,按需搭配。
前端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 可预测)。
前端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处理。 这只是自定义中间件的一个简单例子,但您可以根据需要在中间件中实现更复杂的逻辑,例如异步操作、路由导航或其他您想要的任何自定义行为。