理解Flux
Flux是Facebook推出的一种组织web应用开发的架构思想,它的基本思想很简单:
在你的应用中,数据应该是单向流动的。
这种思想可以被称为”单向数据流”,你也可以把它想象成一条鲨鱼:因为鲨鱼永远只能向前游动。
到目前为止,Facebook自己推出了Flux实践的例子,同时至少有6个JS库进行了Flux架构的实现。在本文中,我们谈到的Flux,特指Facebook实现的Flux。
一个Flux的例子
为了真正理解Flux,我们还是从一个最基本的Todo应用开始。当然,你也可以从Facebook的Flux仓库中找到这个例子。
加载ToDo项目
当应用开始启动时,ToDoApp
这个React组件会从ToDoStore
中获取并展示数据。ToDoStore
则完全不会意识到ToDoApp
的存在。如果你把这个组件想象成一个视图,ToDoStore
则可以看做模型,那么到目前为止,Flux看起来和传统的MVC也没有什么差别。
// 将初始化数据载入应用中:
// ...
/**
* 从ToDoStore种获取当前的TODO数据
*/
function getTodoState() {
return {
allTodos: TodoStore.getAll(),
areAllComplete: TodoStore.areAllComplete()
};
}
var TodoApp = React.createClass({
getInitialState: function() {
return getTodoState();
},
// ...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
代码laycode - v1.1
在这个简单的例子中,我们将不去关心ToDoStore如何载入初始数据。
创建一个新的ToDo项目
ToDoApp组件中包含一个表单用于创建一个新的ToDo项目。当一个用户提交这个表单时,它将启动一个Flux系统种的数据流,具体流程如上图所示:
-
步骤1,组件将通过调用回调函数来处理表单提交这个事件:
// 通过调用 `_onSave` 回调函数来存储一个新的ToDo项目
// ...
var Header = React.createClass({
/**
* @return {object}
*/
render: function() {
return (
<header id="header">
<h1>todos</h1>
<TodoTextInput
id="new-todo"
placeholder="What needs to be done?"
onSave={this._onSave}
/>
</header>
);
},
// ...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
代码laycode - v1.1
-
步骤2,组件的回调函数将会调用ToDoActionCreator
中的一个方法:
// `_onSave`回调函数将会调用`TodoActions`方法来创建一个新的action
// ...
/**
* 事件处理函数在TodoTextInput中被调用
* TosoTextInput在这个定义,并可以在多个地方,以多种方式使用
* @param {string} text
*/
_onSave: function(text) {
if (text.trim()){
TodoActions.create(text);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
代码laycode - v1.1
3.步骤3,ToDoActionCreator创建一个TODO_CREATE类型的action:
// `create`方法创建一个`TODO_CREATE`类型的action
// ...
var TodoActions = {
/**
* @param {string} text
*/
create: function(text) {
AppDispatcher.handleViewAction({
actionType: TodoConstants.TODO_CREATE,
text: text
});
},
// ...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
代码laycode - v1.1
-
步骤4,action被传递给派发器
-
步骤5,派发器将这个action传递给所有来自Store中的注册回调函数:
// `handleViewAction`将action派发给所有的存储
// ...
var Dispatcher = require('flux').Dispatcher;
var assign = require('object-assign');
var AppDispatcher = assign(new Dispatcher(), {
/**
* 一个视图和派发器之间的连接函数,将action视作一个视图action。另一个变量可以是handleServerAction。
* @param {object}
*/
handleViewAction: function(action) {
this.dispatch({
source: 'VIEW_ACTION',
action: action
});
}
});
// ...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
代码laycode - v1.1
6.步骤6,ToDoStore中包含一个注册的回调函数来监听TODO_CREATE action,并更新相应的数据。
// TodoStore中包含一个对应`TODO_CREATE` action的回调函数
// ...
/**
* Create a TODO item.
* @param {string} ToDo项目的内容
*/
function create(text) {
//
// 使用一个当前的时间戳 + 随机数来替代真实的id
var id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
_todos[id] = {
id: id,
complete: false,
text: text
};
}
// 处理所有的数据更新
AppDispatcher.register(function(payload) {
var action = payload.action;
var text;
switch(action.actionType) {
case TodoConstants.TODO_CREATE:
text = action.text.trim();
if (text !== '') {
create(text);
}
break;
// ...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
代码laycode - v1.1
7.步骤7,ToDoStore在更新相应数据之后触发一个change事件:
// TodoStore 在处理完action之后触发一个`change`事件
// ...
// 处理所有的更新
AppDispatcher.register(function(payload) {
var action = payload.action;
var text;
switch(action.actionType) {
case TodoConstants.TODO_CREATE:
text = action.text.trim();
if (text !== '') {
create(text);
}
break;
// ...
default:
return true;
}
// 在触发一个UI change之后通常需要调用这个方法。在每一个视图变化之后我们需要触发一个UI change,因此我们在这个调用这个方法来减少重复的代码。为了确保上述内容的进行,我们需要一个默认的方法。
TodoStore.emitChange();
return true;
});
// ...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
代码laycode - v1.1
8.步骤8,ToDoApp组件监听来自ToDoStore的change事件,然后根据ToDoStore种的最新数据来重新渲染UI
// 这个组件通过调用`_onChange`回调函数来监听change事件
// ...
var TodoApp = React.createClass({
getInitialState: function() {
return getTodoState();
},
componentDidMount: function() {
TodoStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
TodoStore.removeChangeListener(this._onChange);
},
// ...
/**
* 用于处理来自TodoStore中的`change`事件的方法
*/
_onChange: function() {
this.setState(getTodoState());
}
// ...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
代码laycode - v1.1
Flux vs. MVC
Flux被认为是MVC模式的一种替代方案。在Flux的文档中将其解释为“一种使用单向数据流而不是MVC”的应用架构模式。将Flux和MVC相比,你需要理解下面三点:
- 在javascript的世界中,“MVC”意味着”MV*”
- Flux并不会比MV*要简单
- 相较于MV*,Flux模式中的代码更具有可预测性
在javascript的世界中,“MVC”意味着”MV*”
为了类比Flux和MVC,我们需要先理解MVC究竟是什么东西。
在著名的ToDoMVC项目中,至少有15种javascript框架的例子,但是其中没有一个例子是严格实现了”模型,视图,控制器”的设计模式。比如说Backbone.js:它其中包含模型和视图,但是是否存在控制器这个问题却一直饱受争议。在许多javascript框架中,控制器的角色一般都融入了模型或者视图中,对于javascript框架来说还有其他更重要的角色,比如说一个路由。
当我们在使用”MVC”或者”MV*”来描述一个javascript架构时,一般来说指的是将数据层逻辑和用户界面逻辑分开来考虑。数据存储抽象为”模型”,而数据呈现和用户交互抽象为”视图”。
应用运行的流程一般来说是这样的:视图从模型获取数据,展示给用户。用户和界面发生交互。这些交互触发了视图去更新存储在模型中的数据,同时触发一次视图更新。
Flux并不比MV*要简单
用Facebook自己的话来说,”MVC不具有可扩展性”,并同时以下面的图作为佐证:
上面这张图让MVC看起来很让人疑惑 – 居然有这么多的箭头!看起来Flux似乎要更简单一些不是吗?
但是Facebook却在Flux流程图种将复杂度大大减小,如下所示:
如果要实现一个大型的Flux应用,流程图并不简单,如下所示:
相比MVC,Flux并不简单。但是其中有一点关键的不同之处在于:Flux中所有的箭头都指向同一个方向。
Flux中的代码更具有可预测性
虽然Flux并不比MVC简单,但是Flux图表种的可预测性要大大高于MVC图表。
Flux种的派发器确保了系统中一次只会有一个action流。如果一个action还没有处理完,那么这时再派发一个action将会触发一个错误:
这是使得代码可预测性提高的另一种方式。它促使开发者能够开发出让数据源之间的交互变得简单的代码。
派发器也能让开发者指明回调函数执行的顺序,其中会使用waitFor
方法来告诉回调函数依次执行。
在Facebook实现的Flux代码种,你可以明确的看到引发数据变化的部分。每一个store都包含一系列它所监听的action:
// 这个例子表明了store监听的action
// ...
ThreadStore.dispatchToken = ChatAppDispatcher.register(function(payload) {
var action = payload.action;
switch(action.type) {
case ActionTypes.CLICK_THREAD:
_currentID = action.threadID;
_threads[_currentID].lastMessage.isRead = true;
ThreadStore.emitChange();
break;
case ActionTypes.RECEIVE_RAW_MESSAGES:
ThreadStore.init(action.rawMessages);
ThreadStore.emitChange();
break;
default:
// 默认的逻辑
}
});
// ...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
代码laycode - v1.1
在上面这个例子中,ThreadStore监听CLICK_THREAD action,和RECEIVE_RAW_MASSAGES action。如果store没有按照预期那样更新,register回调函数将进行一些调试工作。我们可以打印出接收到的任何action,并监视它们的数据荷载(payload of data)。
与之类似,每一个组件都包含一个它所监听的store的列表:
function getStateFromStores() {
return {
threads: ThreadStore.getAllChrono(),
currentThreadID: ThreadStore.getCurrentID(),
unreadCount: UnreadThreadStore.getCount()
};
}
var ThreadSection = React.createClass({
getInitialState: function() {
return getStateFromStores();
},
componentDidMount: function() {
ThreadStore.addChangeListener(this._onChange);
UnreadThreadStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
ThreadStore.removeChangeListener(this._onChange);
UnreadThreadStore.removeChangeListener(this._onChange);
},
// ...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
代码laycode - v1.1
在上面的代码种,我们可以看到ThreadSection组件监听来自ThreadStore和UnreadThreadStore种的变化。如果我们持续使用这个方法,我们可以保证没有其他的数据来源会影响组件的行为。
Flux将数据的发送和数据的接收分开,因此当你调试应用时,你可以轻松地看到数据的流向,并找出错误究竟发生在哪里。
Flux的难点
没有哪一种模式是完美无缺的,Flux也一样。一般来说,它有以下几个缺点:
- 代码编写更加模板化
- 移植现有代码比较困难
- 单元测试难以进行
为了处理数据流,在Flux应用种我们需要添加更多的文件和代码。和为Flux应用中已经存在的一个数据源添加代码相比,我们为应用添加一个新的数据源将是一件无比痛苦的事情。在将来我们或许可以使用生成器是的Flux代码的设施变得简单。
尝试Flux最简单的方式是开启一个新项目。在新项目种,要将其他的部分移植到Flux架构将是一件非常有挑战的事情。运用从本文和Flux文档中学到的知识,你完全可以像Facebook和其他使用Flux的公司一样,将Flux架构运用到你的项目中。
当你将现有项目移植到Flux架构时,你可以每次为Flux架构添加一个数据源。然而,在尝试使用Flux管理一块数据时,你需要考虑有多少组件会使用这块数据。如果这块数据在大部分的组件中都有被使用,那么将这块数据移植到Flux中将是一件复杂的工作。当你初次尝试Flux时,你应该先用一些独立的数据块作为练习。
在Flux中,你的组件开始依赖ActionCreators以及Store,以及其他的依赖项目。这将使得编写单元测试异常复杂。如果你在应用种将Store的交互严格限制在顶级的”controller”上,那么你可以对子组件进行单元测试而无需担心Stores。如果你要测试那些需要发送Actions以及监听Stores的组件,我们需要模拟一些Store种方法,或者模拟Actions和Stores用于接收数据的API。
相关推荐
React通量锅炉板简单的 React Flux Boiler 板来理解 Flux 架构的概念。 应用程序很简单,在按钮上单击我们正在生成随机数,并且生成的内容正在以“/”分隔的方式附加到 UI 中。 我们在这里学到的概念是组件之间的...
理解Flux协议流量的关键在于掌握以下几个核心概念: 1. **数据源**:Flux协议通常从各种设备或传感器获取数据,这些设备作为数据源不断地产生时间序列数据。例如,IoT设备会周期性地发送温度、湿度或其他环境指标。...
首先,让我们深入理解Flux。Flux代表的是0到无限个数据发射的序列。它可以用来处理事件流、数据库查询结果等任何可以被视为一系列数据项的场景。Flux提供了丰富的操作符,如map、filter、concatMap、flatMap等,使得...
网上资料太少了,flux并不是框架,而是可以理解为一种模式。内含明细的代码注释。 代码结构说明 app.js ==> 入口程序,实现flux中的view store.js ==> 实现flux中的store action.js==>事件处理器。实现flux中的...
在深入探讨Flux Filename Service的源码之前,首先需要理解Flux和Filename Service的基本概念。Flux是Spring框架中的一个关键组件,它是响应式编程模型的一部分,用于处理数据流。而Filename Service则可能是负责...
学习和使用这个小型Flux实现,开发者可以更好地理解Flux的基本工作原理,从而在更复杂的项目中运用或扩展。对于初学者来说,这是一个很好的起点,可以避免被大型库的复杂性所困扰,同时也能掌握一种有效的状态管理...
尽管Redux在许多方面取代了Flux,但理解Flux的原理对于理解Redux的运作仍然很有帮助。 4. **Webpack**:Webpack是一个模块打包器,可以将各种资源(如JavaScript、CSS、图片等)视为模块,并根据依赖关系进行打包。...
6. **调试工具**:为了帮助开发者更好地理解和调试Flux架构,插件还提供了一些辅助工具,如日志输出和可视化调试。 使用Flux Unity插件进行技能编辑,开发者可以轻松地创建和管理角色的技能树、冷却时间、伤害计算...
**Flux屏幕色温调整工具详解** 在长时间使用电子设备,特别是电脑屏幕时,人们往往会感到眼睛疲劳,甚至...为了更好地利用Flux,用户可以下载名为"flux_chs"的中文语言包,以便在使用过程中更好地理解和设置各项功能。
培训目标包括让用户熟悉系统操作,理解系统逻辑,掌握日常作业流程,以实现高效协同工作。 **六、结论** FLUX WMS作为一款先进的仓储管理系统,通过精细化的流程控制和强大的功能,帮助企业提升仓库管理效能,降低...
通过研究这个项目,开发者可以深入理解 Flux 架构的工作原理,学习如何组织代码以实现可维护性和可扩展性。同时,这也是一个很好的实践平台,有助于提升使用 JavaScript 和 Flux 构建 Web 应用的能力。
"Flux"是一个在设计和开发领域中广泛使用的术语,特别是在UI(用户界面)和UX(用户体验...结合字体相关的标签,我们可以理解为Flux架构不仅处理应用程序的逻辑,还与UI设计中的视觉元素,如字体选择和样式,紧密关联。
与Facebook的官方 Flux库相比,Fluxify可能没有那么多附加功能,但它的简洁性有助于初学者快速理解Flux的工作原理。 在解压后的"fluxify-master"文件夹中,你可能会找到以下组成部分: - `dispatcher.js`:...
首先,理解Flux架构的基本组成部分: 1. **Dispatcher**:Dispatcher是Flux架构的中心,它负责协调各个Store(数据存储)之间的交互。当用户触发Action(操作)时,Dispatcher会广播这个Action给所有注册的Store。 ...
在深入探讨 Flux 下载和使用之前,我们先理解一下 Flux 的核心概念: 1. **GitOps**:Flux 是 GitOps 的一种实现,它的核心理念是将集群的状态(如应用配置、服务定义等)都存储在 Git 仓库中,通过 Git 的版本控制...
这种设计避免了复杂的双向绑定,使得应用程序的状态变化更易于理解和调试。 **Retrofit** 是Square公司开发的一个Java和Kotlin的网络请求库,它允许开发者通过简单的接口定义HTTP服务。Retrofit通过动态代码生成,...
在压缩包文件"selvinortiz-flux-9deb185"中,包含了Flux库的源代码,版本号为9deb185。开发者可以下载并研究其源代码,了解其实现细节,或者直接将库引入到项目中使用。在实际应用中,确保正确安装和配置Flux,并...
标题中的“Heat Flux.rar_UDF 温度_fluent_fluent udf_flux_udf边界”揭示了这个压缩包文件的核心内容,主要涉及Fluent软件中用户自定义函数(UDF)的编写,目的是为了定义和控制热流边界条件,特别是在固体表面温度...