在应用或软件开发的初期阶段,我们首要的任务是收集并深入理解客户的需求,以此为基石构建解决方案,从而切实解决客户或企业面临的痛点。为了解决这些问题,我们往往需要依赖成熟的技术栈和经过验证的架构模式。在很长一段时间里,MVC(模型-视图-控制器)模式一直是业界的标准选择。然而,随着技术的演进,我们发现 MVC 在处理复杂的前端状态时,往往会带来难以维护的代码结构和不可预测的数据流。
正是为了解决 MVC 的这些局限性,Facebook 的开发团队提出了重要的变革,发布了 Flux 架构。Flux 彻底改变了数据在应用中的流转方式。随后,基于 Flux 的核心理念,市场上又出现了一个更加强大且流行的状态管理框架——Redux。作为开发者,你可能会好奇:这两者究竟有何本质区别?我们应该如何选择?在本文中,我们将深入探讨 Redux 与 Flux 的区别,并通过构建一个待办事项(TODO)应用,带你一步步掌握它们在 ReactJS 中的实战用法。
Flux 与 Redux:架构理念的演进
在正式编写代码之前,让我们先从宏观的角度理解这两种架构。
Flux 是什么?
Flux 并不是一个具体的库或框架,而是一种应用架构模式,或者我们可以说是一种用于构建客户端 Web 应用程序的 JavaScript 架构思想。它的核心在于“单向数据流”。Flux 克服了 MVC 模式在处理双向数据绑定时带来的不稳定性和复杂性(比如当模型和视图相互依赖时,数据流向会变得混乱)。在 Flux 架构中,数据只能在一个方向上流动:Action -> Dispatcher -> Store -> View。
Redux 是什么?
Redux 可以说是 Flux 架构思想的一种进化实现。它借鉴了 Elm 架构,并遵循三大核心原则:单一数据源、状态只读和使用纯函数执行修改。虽然 Redux 不像传统 Flux 那样强制依赖一个 Dispatcher,但它在简化 API、提供可预测的状态管理以及强大的中间件支持(如日志、异步处理)方面做得更出色。
核心区别:Redux 与 Flux 的对比
为了让你更直观地理解,让我们看看它们在几个关键点上的不同:
- Dispatcher(调度器)的存在:
* Flux:拥有一个单例的 Dispatcher。它是 Action 和 Store 之间的中心枢纽,负责将 Action 分发到所有注册的回调中。
* Redux:没有 Dispatcher。Redux 直接使用纯函数来处理逻辑。当你 dispatch 一个 action 时,Redux 会把它直接传给 Root Reducer,而不需要经过一个中心化的调度器对象。这使得代码更简洁。
- Store(存储)的数量:
* Flux:应用中可以有多个 Store。例如,你可以有一个 INLINECODE6fabdefa 来管理任务,一个 INLINECODE0099616f 来管理用户信息。每个 Store 独立管理自己的状态。
* Redux:推崇 单一 Store。整个应用的状态被存储在一个单一的对象树中。这种方式使得调试变得非常容易(比如你可以轻松实现“时间旅行调试”),状态管理也更加集中。
- 逻辑处理方式:
* Flux:逻辑通常包含在 Store 内部,Store 监听 Dispatcher 的动作并更新自身。
* Redux:逻辑封装在 Reducers 中。Reducers 是纯函数,接收旧的 state 和 action,返回新的 state。这种纯函数特性使得逻辑测试变得异常简单。
实战演示:构建 TODO 应用
为了让你不仅“知道”而且“会用”,让我们通过构建一个待办事项列表 应用来实践这两种架构的不同实现方式。这个应用包含以下功能:
- 添加新任务
- 删除现有任务
我们将重点展示代码结构上的差异,让你感受到 Redux 的简洁性。
#### Flux 架构实现步骤
在 Flux 模式下,我们需要手动搭建 Dispatcher、Actions 和 Stores。
步骤 1:初始化项目与依赖
首先,我们需要创建一个新的 React 应用,并安装必要的 Flux 库(虽然 Flux 主要是架构模式,但我们可以使用 Facebook 官方的 flux 库来辅助实现 Dispatcher)。打开终端运行以下命令:
# 创建 React 应用
npx create-react-app todo-flux-app
# 进入目录并安装 flux 依赖
cd todo-flux-app
npm install flux --save
步骤 2:创建项目结构
Flux 强调关注点分离。建议在你的 src 目录下创建以下文件夹结构:
-
actions/: 存放动作创建函数 -
dispatcher/: 存放调度器实例 -
stores/: 存放数据存储逻辑 -
components/: 存放 React 组件
步骤 3:定义 Dispatcher(调度器)
在 Flux 中,Dispatcher 是中心枢纽。创建一个 dispatcher.js 文件:
// src/dispatcher/dispatcher.js
import { Dispatcher } from "flux";
// 导出一个全局唯一的 Dispatcher 实例
export default new Dispatcher();
步骤 4:创建 Actions(动作)
Action 是应用生命周期中发生的事情的描述。在我们的应用中,当用户点击“创建”或“删除”按钮时,就会触发 Action。
// src/actions/TodoActions.js
import dispatcher from "../dispatcher/dispatcher";
/* 创建任务函数 */
export function createTodo(text) {
// 使用 dispatch 方法将动作发送给 Dispatcher
dispatcher.dispatch({
type: "CREATE_TODO",
text,
});
}
/* 删除任务函数 */
export function deleteTodo(id) {
dispatcher.dispatch({
type: "DELETE_TODO",
id,
});
}
步骤 5:实现 Store(存储)
这是 Flux 的核心部分。Store 负责保存数据和处理业务逻辑。我们需要使用 Node.js 的 EventEmitter 来让 Store 在数据变化时通知 View(React 组件)。
// src/stores/TodoStore.js
import { EventEmitter } from ‘events‘;
import dispatcher from ‘../dispatcher/dispatcher‘;
class TodoStore extends EventEmitter {
constructor() {
super();
// 初始化一些模拟数据
this.todos = [
{ id: 16561, text: ‘学习 Flux 架构‘ },
{ id: 16562, text: ‘掌握 Redux 技巧‘ },
];
}
// 创建任务的具体逻辑
createTodo(text) {
const id = Date.now();
this.todos.push({
id,
text
});
// 触发 change 事件,通知 React 组件更新
this.emit(‘change‘);
}
// 删除任务的具体逻辑
deleteTodo(id) {
// 过滤掉指定 ID 的任务
this.todos = this.todos.filter((elm) => {
return (elm.id !== id);
});
this.emit(‘change‘);
}
// 获取所有任务的公共方法
getAll() {
return this.todos;
}
// 处理来自 Dispatcher 的动作
handleActions(action) {
switch (action.type) {
case ‘CREATE_TODO‘: {
this.createTodo(action.text);
break;
}
case ‘DELETE_TODO‘: {
this.deleteTodo(action.id);
break;
}
}
}
}
const todoStore = new TodoStore();
// 注册 Store 到 Dispatcher,让 Dispatcher 知道要把动作发给谁
dispatcher.register(todoStore.handleActions.bind(todoStore));
export default todoStore;
你看到了吗?在 Flux 的实现中,我们需要在 Store 内部编写 switch 语句来处理逻辑,并手动管理事件的监听与触发。这在大型应用中可能会增加代码量。
#### Redux 架构实现步骤
现在,让我们看看用 Redux 如何实现同样的功能。你会发现代码变得更加结构化。
步骤 1:安装 Redux 依赖
我们需要安装 INLINECODE550aee86 和 INLINECODEf44e06b6(用于连接 React 和 Redux)。
npm install redux react-redux --save
步骤 2:定义 Action Types(常量)
虽然 Redux 中不强制要求,但将 Action Types 定义为常量是一种最佳实践,可以避免拼写错误。
// src/constants/actionTypes.js
export const CREATE_TODO = ‘CREATE_TODO‘;
export const DELETE_TODO = ‘DELETE_TODO‘;
步骤 3:编写 Reducers(纯函数)
注意,这里没有 INLINECODEf34ef8ce,没有 INLINECODEc09c70b3 注册,只有纯函数逻辑。Redux 将“如何改变状态”的逻辑与“事件监听”完全解耦了。
// src/reducers/todoReducer.js
import { CREATE_TODO, DELETE_TODO } from ‘../constants/actionTypes‘;
// 初始化 State
const initialState = [
{ id: 1, text: ‘学习 Redux‘ },
{ id: 2, text: ‘编写代码‘ }
];
// Reducer 是一个纯函数,接收旧状态和动作,返回新状态
export default function todoReducer(state = initialState, action) {
switch (action.type) {
case CREATE_TODO:
// 返回包含新任务的新数组(不可变数据更新)
return [
...state,
{
id: action.id,
text: action.text
}
];
case DELETE_TODO:
// 过滤掉被删除的任务
return state.filter(todo => todo.id !== action.id);
default:
return state;
}
}
步骤 4:创建 Store 与 Action Creators
在 Redux 中,Store 的创建非常统一。同时,我们依然需要 Action Creators 来生成动作对象。
// src/actions/todoActions.js
import { CREATE_TODO, DELETE_TODO } from ‘../constants/actionTypes‘;
export const createTodo = (text) => {
return {
type: CREATE_TODO,
id: Date.now(), // 在 Action 创建时生成 ID
text
};
};
export const deleteTodo = (id) => {
return {
type: DELETE_TODO,
id
};
};
步骤 5:连接 React 组件(Connect)
这是 Redux 与 React 交互的关键。我们使用 INLINECODEc5a01543 包裹应用,并用 INLINECODE0b8891a8 高阶组件将数据注入到组件中。
// src/components/TodoList.js (示例)
import React from ‘react‘;
import { connect } from ‘react-redux‘;
import { createTodo, deleteTodo } from ‘../actions/todoActions‘;
function TodoList({ todos, onCreate, onDelete }) {
const [inputValue, setInputValue] = React.useState(‘‘);
const handleAdd = () => {
if (inputValue.trim()) {
onCreate(inputValue);
setInputValue(‘‘);
}
};
return (
我的待办事项 (Redux版)
setInputValue(e.target.value)}
/>
{todos.map(todo => (
-
{todo.text}
))}
);
}
// 将 State 映射到 Props
const mapStateToProps = (state) => ({
todos: state // 假设我们只有一个 reducer
});
// 将 Action Creators 映射到 Props
const mapDispatchToProps = (dispatch) => ({
onCreate: (text) => dispatch(createTodo(text)),
onDelete: (id) => dispatch(deleteTodo(id))
});
// 使用 connect 连接组件
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
深入探讨:为何 Redux 变得如此流行?
通过上面的代码对比,我们可以看到 Redux 带来的一些显著优势:
- 可预测性:因为 Reducer 是纯函数,相同的输入永远得到相同的输出。这使得我们可以轻松地复现任何状态场景。
- 中心化:所有的状态都在一个 Store 树中。当我们需要调试时,不需要去翻阅各个分散的 Store 文件,只需要查看当前的 State Tree。
- 生态丰富:Redux 强调中间件的概念。如果你需要处理异步操作(比如 AJAX 请求),你可以引入 INLINECODE56c340bf 或 INLINECODE9798da1a。这使得 Redux 能够极其灵活地应对各种复杂业务场景,而无需修改核心架构代码。
常见问题与解决方案
在开发过程中,你可能会遇到一些常见问题,这里提供一些经验之谈:
- 在 React 中何时使用 Redux?
不要在所有项目中都强行使用 Redux。如果你只是做一个小型的表单提交 demo,React 自带的 INLINECODEc159cbb3 或 INLINECODE5f20c859 就足够了。Redux 最适合用在“多组件共享状态”和“复杂交互逻辑”的场景中。
- 如何处理嵌套数据?
Redux 提倡数据的规范化。不要把数据存成深层嵌套的 JSON 结构。尽量将其“拍平”,像数据库表一样存储,这样更新数据时不会因为修改父对象而导致所有子对象引用的变化,从而优化 React 的渲染性能。
- React 18 的并发模式与 Redux
现代 Redux(Redux Toolkit)已经针对 React 18 的并发特性进行了大量优化,比如使用了 useSyncExternalStore 来确保状态读取的稳定性。如果你正在开始新项目,建议直接使用 Redux Toolkit,它简化了 Boilerplate(样板代码),并内置了最佳实践。
总结与后续步骤
在这篇文章中,我们一起探索了 Flux 与 Redux 的区别,并从零开始构建了两个版本的 TODO 应用。
- Flux 是一种伟大的思想,它教会了我们单向数据流的重要性,但在代码实现上略显繁琐。
- Redux 继承了 Flux 的衣钵,通过纯函数、单一 Store 和强大的中间件机制,成为了 React 生态中最主流的状态管理方案。
给您的建议:
如果你已经掌握了基础的概念,我建议你下一步尝试探索 Redux Toolkit。它是现在官方推荐编写 Redux 的方式,它会帮你解决配置 Store、编写 Immutability 不可变数据逻辑时的繁琐问题。试着在你的下一个项目中,引入 Redux Toolkit 来管理全局状态,体验一下高效开发的乐趣吧!