`

理解Flux

 
阅读更多

理解Flux

Flux是Facebook推出的一种组织web应用开发的架构思想,它的基本思想很简单:

在你的应用中,数据应该是单向流动的。

这种思想可以被称为”单向数据流”,你也可以把它想象成一条鲨鱼:因为鲨鱼永远只能向前游动。

到目前为止,Facebook自己推出了Flux实践的例子,同时至少有6个JS库进行了Flux架构的实现。在本文中,我们谈到的Flux,特指Facebook实现的Flux。

一个Flux的例子

为了真正理解Flux,我们还是从一个最基本的Todo应用开始。当然,你也可以从Facebook的Flux仓库中找到这个例子。

加载ToDo项目

理解Flux

当应用开始启动时,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. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
代码laycode - v1.1

在这个简单的例子中,我们将不去关心ToDoStore如何载入初始数据。

创建一个新的ToDo项目

理解Flux

ToDoApp组件中包含一个表单用于创建一个新的ToDo项目。当一个用户提交这个表单时,它将启动一个Flux系统种的数据流,具体流程如上图所示:

  1. 步骤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. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. 6
    7. 7
    8. 8
    9. 9
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    代码laycode - v1.1
  2. 步骤2,组件的回调函数将会调用ToDoActionCreator中的一个方法:

    // `_onSave`回调函数将会调用`TodoActions`方法来创建一个新的action
      // ...
      /**
      * 事件处理函数在TodoTextInput中被调用
      * TosoTextInput在这个定义,并可以在多个地方,以多种方式使用
     * @param {string} text
      */
     _onSave: function(text) {
      if (text.trim()){
      TodoActions.create(text);
    }
    }
    
    1. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. 6
    7. 7
    8. 8
    9. 9
    10. 10
    11. 11
    12. 12
    13. 13
    14. 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. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. 6
    7. 7
    8. 8
    9. 9
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    代码laycode - v1.1
    1. 步骤4,action被传递给派发器

    2. 步骤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. 1
      2. 2
      3. 3
      4. 4
      5. 5
      6. 6
      7. 7
      8. 8
      9. 9
      10. 10
      11. 11
      12. 12
      13. 13
      14. 14
      15. 15
      16. 16
      17. 17
      18. 18
      19. 19
      20. 20
      21. 21
      22. 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. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. 6
    7. 7
    8. 8
    9. 9
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    27. 27
    28. 28
    29. 29
    30. 30
    31. 31
    32. 32
    33. 33
    34. 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. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. 6
    7. 7
    8. 8
    9. 9
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    27. 27
    28. 28
    29. 29
    30. 30
    31. 31
    32. 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. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. 6
    7. 7
    8. 8
    9. 9
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    代码laycode - v1.1

Flux vs. MVC

Flux被认为是MVC模式的一种替代方案。在Flux的文档中将其解释为“一种使用单向数据流而不是MVC”的应用架构模式。将Flux和MVC相比,你需要理解下面三点:

  1. 在javascript的世界中,“MVC”意味着”MV*”
  2. Flux并不会比MV*要简单
  3. 相较于MV*,Flux模式中的代码更具有可预测性

在javascript的世界中,“MVC”意味着”MV*”

为了类比Flux和MVC,我们需要先理解MVC究竟是什么东西。

在著名的ToDoMVC项目中,至少有15种javascript框架的例子,但是其中没有一个例子是严格实现了”模型,视图,控制器”的设计模式。比如说Backbone.js:它其中包含模型和视图,但是是否存在控制器这个问题却一直饱受争议。在许多javascript框架中,控制器的角色一般都融入了模型或者视图中,对于javascript框架来说还有其他更重要的角色,比如说一个路由。

当我们在使用”MVC”或者”MV*”来描述一个javascript架构时,一般来说指的是将数据层逻辑和用户界面逻辑分开来考虑。数据存储抽象为”模型”,而数据呈现和用户交互抽象为”视图”。

应用运行的流程一般来说是这样的:视图从模型获取数据,展示给用户。用户和界面发生交互。这些交互触发了视图去更新存储在模型中的数据,同时触发一次视图更新。

理解Flux

Flux并不比MV*要简单

用Facebook自己的话来说,”MVC不具有可扩展性”,并同时以下面的图作为佐证:

理解Flux

上面这张图让MVC看起来很让人疑惑 – 居然有这么多的箭头!看起来Flux似乎要更简单一些不是吗?

但是Facebook却在Flux流程图种将复杂度大大减小,如下所示:

理解Flux

如果要实现一个大型的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. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 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. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 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-flux-boiler-plate:简单的 React Flux Boiler 板来理解 Flux 架构的概念

    React通量锅炉板简单的 React Flux Boiler 板来理解 Flux 架构的概念。 应用程序很简单,在按钮上单击我们正在生成随机数,并且生成的内容正在以“/”分隔的方式附加到 UI 中。 我们在这里学到的概念是组件之间的...

    easy-flux:写了一个非常简单的flux例子,方便大家理解flux

    网上资料太少了,flux并不是框架,而是可以理解为一种模式。内含明细的代码注释。 代码结构说明 app.js ==&gt; 入口程序,实现flux中的view store.js ==&gt; 实现flux中的store action.js==&gt;事件处理器。实现flux中的...

    Flux Architecture.pdf

    虽然Reac已经成为Facebook清洁、复杂和现代网络发展的典范,但它却以其简单性为基础。...充满充分工作的例子和代码的第一解释,在书的结尾,你将不仅有一个坚实的理解体系结构,而且将准备好在愤怒中实现Flux体系结构。

    React Flux的一些理解1

    React Flux的一些理解1

    Android学习之Flux架构入门

    Flux 架构介绍 Flux 架构 被Facebook使用来构建他们的客户端web应用。跟Clean Architecture一样,它不是为移动应用设计的,但是它的特性和简单可以让我们很好的在...要理解Flux,有两个关键的特点 1、数据流总是单向的

    why-flux:将一个小应用程序重构为 Flux 以了解为什么我们需要 Flux 的所有部分

    最好的理解 Flux 是得出结论,我们需要与 Flux 完全一样的东西。 在这个应用程序中,我们将使用 Flux 慢慢地将一个典型的不断增长的 React 应用程序重构为一个,它会(希望)感觉自然。 粗略的议程: 引入控制器...

    最近学习reactreflux的练手项目一边学一边写

    最近学习react、reflux的练手项目,一边学一边写,还不大理解flux……

    personDB:简单的Web应用程序,用于向数据库中添加和删除人员

    学习助焊剂架构从上的样板开始,这是在不依赖Facebook开始的一切前提下理解Flux架构的尝试。 npm install -g gulpnpm install用于建筑 gulp 默认的gulp将使源代码不完整,使用带有JSX编译的react转换运行browserify...

    FOR_FLUX_SAKE:将flux与react结合使用的初学者教程

    本教程并非旨在使您全面掌握Flux的能力,但它旨在弥合不了解Flux和能够在线理解大多数教程之间的巨大差距。 它试图通过将您希望已经理解的东西缓慢地转变为根据Flux架构原理构建的东西(听起来多么宏大)来做到这...

    simple-flux-dispatcher

    的工作方式与助焊剂文档中所述的相同,但更简单,并带有易于理解的错误消息 ##安装 npm install --save-dev simple-flux-dispatcher 用法 var simpleDispatcher = require ( 'simple-flux-dispatcher' ) ; var...

    在knockoutjs 上自己实现的flux(实例讲解)

    在knockoutjs 上实现 Flux 单向数据流 状态机,主要解决多个组件之间对数据的耦合...而我在设计ko的Flux时,去掉了Mutation这个环节,是因为我理解为,异步的请求一般情况下都是与api接口有关系,这块内容存在极大的变

    ReactTagCloud:React、Vanilla Flux、React Hot Reloader、Webpack

    遵循 Flux-Pattern 会增加一些开销,但可以实现非常干净且可预测的可理解代码 - 保存以增长。 一旦开发人员掌握了 Flux 的概念和工作模式,它就可以非常快速地理解未知代码。 它还支持热重载功能,因为所有状态始终...

    Vue的Flux框架之Vuex状态管理器

    学习vue之前,最重要是弄懂两个概念,一是“what”,要理解vuex是什么;二是“why”,要清楚为什么要用vuex。 Vuex是什么? Vuex 类似 React 里面的 Redux 的状态管理器,用来管理Vue的所有组件状态。 为什么使用...

    docker.docx

    本文是Flux7的Docker系列教程的第一部分。请和这份教程一起学习和理解Docker有什么优势以及如何更好地使用它。 让我们一起来学习Docker。 本文主要涉及Docker的基础知识:Docker的特征、理念以及如何安装使用Docker

    k8s-cluster:使用Flux和Helm Operator管理Helm版本

    管理头盔发布了GitOps方式什么是GitOps? GitOps是一种进行持续交付的方法,它通过将Git... 为了更好地理解这种CD方式的好处以及GitOps和“基础结构即代码”工具之间的区别,请访问Weaveworks网站并阅读文章。 为了将Gi

    深入REACT技术栈

    覆盖React、Flux、Redux及可视化,帮助开发者在实践中深入理解技术和源码 前端组件化主流解决方案,一本书玩转React“全家桶” 本书讲解了非常多的内容,不仅介绍了面向普通用户的API、应用架构和周边工具,还深入...

    深入REACT 技术栈

    覆盖React、Flux、Redux及可视化,帮助开发者在实践中深入理解技术和源码,前端组件化主流解决方案,一本书玩转React“全家桶”。 本书讲解了非常多的内容,不仅介绍了面向普通用户的API、应用架构和周边工具,还深入...

    react-router-sample:更好地理解React路由器的示例应用程序

    我对在 Flux 架构中使用 react 很感兴趣,这就是为什么我想在确定路由器之前评估一些可用的路由器。 我想选择与 Flux 最兼容的路由器 目标 了解如何使用 react-router 了解嵌套路由的实际含义 我如何处理重定向、...

    深入react技术栈源码

    覆盖React、Flux、Redux及可视化,帮助开发者在实践中深入理解技术和源码 前端组件化主流解决方案,一本书玩转React“全家桶” 本书讲解了非常多的内容,不仅介绍了面向普通用户的API、应用架构和周边工具,还深入...

Global site tag (gtag.js) - Google Analytics