浅谈Flux架构及Redux实践

简介:

Flux概述

Flux是Facebook用来构建用户端的Web应用程序的体系架构,与其它形式化的框架相比,它更像是一个架构思想,用于管理和控制应用中数据的流向。这里应用中的数据指包括但不限于来自服务端的数据页面中view的一些状态(如一个面板是展开还是关闭),临时存储在本地需要持久化到服务端的数据等。

好了,说了这么多好像还是一脸懵逼,不慌,接下来看看展开式。

MVC

在讲述Flux之前,我们看看之前传统的MVC架构以及在前端中的一些问题继而思考Flux带来的改变。MVC(Model-View-Controller)最先兴起于后端,通过对应用程序复杂度的简化使程序更加直观和便于维护。后端程序MVC中View可以看为数据的呈现,Model为数据的模型,Controller作为程序的流程控制。现在假设有这样的场景,用户想查看自己的profile页面,可能会有这样的流程:在页面上点击profile按钮,接下来就是一个HTTP请求(/profile?username=jiavan) => Controller接收到这一请求并获得请求的内容username=jiavan然后告知Model需要jiavan的数据 => Model返回了jiavan的数据 => Controller得到数据返回新的视图,看下流程:

现在前端中又有这样的场景:切换Menu中的Item,当前选中的Item颜色不同于其它颜色并且底部显示对应Item的内容。一般情况下我们会定义一个css class来作为当前选中Item的样式。当用户点击Item_A为被点击的元素新增高亮的class,其它兄弟元素移除该样式,这里的事件响应函数就是Controller,我们会在这里处理样式修改逻辑,以及更新Model的数据,然后新的数据及样式重新渲染界面。这种VC<->M的形式在关系比较简单的情况下是比较清晰容易控制的,但是复杂的页面上这样的模式可能会变得非常混乱:

之所以变得混乱了,因为很多view都具备修改多个model的能力,这里的单个修改行为可以称之为一个Action,一个Action的产生可能是用户行为,或者一个Ajax请求需要渲染新界面。对比上面后端传统MVC模式可以发现:

  • 后端中Action作为一个URL请求,前端中可能是一个事件;
  • 后端中Action处理被集中在Controller中,而前端中是分散的。

那么是不是可以把前端中修改状态即state的行为(事件回调/Ajax)全部抽象成一种Action描述,然后交付到一处即Reducers来进行原子化处理,然后Reducer修改整个应用中唯一的一棵state tree即Store,最后通过state->view的机制来重新渲染?

Flux数据流框架

上面提到的几个概念已经对Flux有了初步的了解,下面进入正题。相信有了解Flux的都应该看过下面这张著名的数据流图:

  1. Action可以看成是修改Store的行为抽象;
  2. Dispatcher管理着应用的数据流,可以看为Action到Store的分发器;
  3. Store管理着整个应用的状态和逻辑,类似MVC中的Model。

所以Flux可以被看作传统MVC的改进而非颠覆,当我第一次看到Flux的时候其实是比较懵逼,但看到并使用了Redux后确实有一种非常惊艳的感觉。

Redux

按照Redux官方的描述Redux is a predictable state container for JavaScript apps.,其中predictable和state container体现了它的作用。那么如何来理解可预测化的呢?这里会有一些函数式编程方面的思想,在Redux中reducer函数是一个纯函数,相同输入一定会是一致的输出,所以确定输入的state那么reducer函数输出的state一定是可以被预测的,因为它只会进行单纯的计算,保证正确的输出。状态容器又是什么?说明Redux有一个专门管理state的地方,就是Store,并且一般情况下是唯一的,应用中所有state形成的一颗状态树就是Store。Redux由Flux演变而来,但受 Elm 的启发,避开了 Flux 的复杂性,我们看看其数据流向:

不同于Flux架构,Redux中没有dispatcher这个概念,并且Redux设想你永远不会变动你的数据,你应该在reducer中返回新的对象来作为应用的新状态。但是它们都可以用(state, action) => newState来表述其核心思想,所以Redux可以被看成是Flux思想的一种实现,但是在细节上会有一些差异。

重要概念

  1. 应用中的所有state都以一个object tree的形式存储在一个单一的store中;
  2. 唯一能改store的方法是触发action,action是动作行为的抽象;
  3. 为了描述action如何改变state树,需要编写reducer函数。

这里需要说明一点的是reducer函数,它应当是一个纯函数,不应该有副作用,不应有API调用,Date.now()或者随机获取等不稳定的操作,应当保证相同的输入reducer计算的结果应该是一致的输出,它只会进行单纯的计算。编写reducer函数也是Redux中比较重要的一块,它的形式如下:

 
  1. function testReducer(state, action) { 
  2.   switch (action.type) { 
  3.     case ACTION_TYPE: 
  4.       // calc... 
  5.       return newState; 
  6.     defaultreturn state; 
  7.   } 
  8.   return newState; 
  9. }  

state是不可修改的,所以返回的新state应该是基于输入state副本的修改,而不是直接修改state后的返回。

原则

1. 单一数据源,store

整个应用的state被存放在一棵Object tree中,并且这个Object tree只存在唯一一个store中;

2. state是只读的

唯一能改变state的方法是触发action,action是对已经发生了的事情的抽象描述,简单的讲,它把行为抽象成了一个对象。

比如,删除一条记录的action可以抽象的理解为:

 
  1.   type: 'DELETE_ITEM'
  2.   index: 1, 
  3. }  

3. 使用纯函数来实现state归并操作,reducer

传入待修改的state和一个告知reducer如何修改state的action,reducer将返回action规则对应下操作后的新的state。

reducer(state, action) => new state

数据流

严格的单向数据流是Redux设计的核心

Redux应用数据的生命周期遵循下面4个步骤:

  1. 调用store.dispatch(action), 可以在任何地方进行;
  2. Redux store调用传入的reducer函数,并且将当前的state树与action传入。reducer是纯函数,只用于计算下一个state,它应该是完全可被预测的,相同的输入必定会有相同的输出,不能有副作用的操作,如API的调用或者路由跳转,这些应该都是在dispatch前产生;
  3. 根reducer将多个子reducer输出合并成一个单一的state树;
  4. Redux store保存了根reducer返回的完整的state树。
    新的state树就是应用的下一个状态,现在就可以根据新的state tree来渲染UI。

Redux实践

我们通过一个非常简单的计数器demo来梳理Redux的数据流。

0x00. 创建action

action其实就是一个普通的对象,只是对行为的抽象描述,这里我们可以把加上一个数描述为:

 
  1.   type: INCREMENT, //该动作的抽象描述 
  2.   number, // 该动作携带的数据 
  3. }  

更多的时候我们会通过一个action生成函数来得到一个action:

 
  1. function incrementCreator(number) { 
  2.   return { 
  3.     type: INCREMENT, 
  4.     number, 
  5.   }; 
  6. }  

0x01. 创建reducer函数

reducer作为整个Redux中action的处理中枢,接收state与action并对此修改数据,返回应用的下一个状态。

 
  1. function countReducer(state, action) { 
  2.   switch (action.type) { 
  3.     case INCREMENT: 
  4.       return Object.assign({}, { 
  5.         counter: state.counter + action.number, 
  6.       }); 
  7.     case DECREMENT: 
  8.       return Object.assign({}, { 
  9.         counter: state.counter - action.number, 
  10.       }); 
  11.     defaultreturn state; 
  12.   } 
  13. }  

注意:上面我们已经提到多次,state是不可修改的,所以通过assign归并我们对数据的操作,返回的是state副本修改后的对象,并非直接修改了输入的state。

0x02. 创建唯一store

通过Redux中的createStore方法传入reducer函数来创建整个应用的store。

 
  1. const store = createStore(countReducer); 

0x03. 修改state

通过store的dispatch方法来发起一个action。

 
  1. store.dispatch(incrementCreator(5)); 
  2.  
  3. store.dispatch(decrementCreator(4));  

完整demo

 
  1. import { createStore } from 'redux'
  2.  
  3. // actions 
  4. const INCREMENT = 'INCREMENT'
  5. const DECREMENT = 'DECREMENT'
  6.  
  7. // actionCreator,可以视为创建action的语法糖 
  8. function incrementCreator(number) { 
  9.   return { 
  10.     type: INCREMENT, 
  11.     number, 
  12.   }; 
  13.  
  14. function decrementCreator(number) { 
  15.   return { 
  16.     type: DECREMENT, 
  17.     number, 
  18.   }; 
  19.  
  20. // 初始化state 
  21. const initialState = { 
  22.   counter: 0, 
  23. }; 
  24.  
  25. // reducers函数,注意最后一定要return state防止不能匹配到action的时候state丢失 
  26. function countReducer(state = initialState, action) { 
  27.   switch (action.type) { 
  28.     case INCREMENT: 
  29.       return Object.assign({}, { 
  30.         counter: state.counter + action.number, 
  31.       }); 
  32.     case DECREMENT: 
  33.       return Object.assign({}, { 
  34.         counter: state.counter - action.number, 
  35.       }); 
  36.     defaultreturn state; 
  37.   } 
  38.  
  39. // 创建store 
  40. const store = createStore(countReducer); 
  41.  
  42. // 订阅store的修改 
  43. const unsubscribe = store.subscribe(function log() { 
  44.   console.log(store.getState()); 
  45. }); 
  46.  
  47. // 通过dispatch action来改变state 
  48. store.dispatch(incrementCreator(5)); //Object {counter: 5} 
  49. store.dispatch(decrementCreator(4)); //Object {counter: 1} 
  50.  
  51. // 取消订阅 
  52. unsubscribe();  



作者:Jiavan

来源:51CTO

相关文章
|
30天前
|
负载均衡 测试技术 持续交付
高效后端开发实践:构建可扩展的微服务架构
在当今快速发展的互联网时代,后端开发扮演着至关重要的角色。本文将重点探讨如何构建可扩展的微服务架构,以及在后端开发中提高效率的一些实践方法。通过合理的架构设计和技术选型,我们可以更好地应对日益复杂的业务需求,实现高效可靠的后端系统。
|
1月前
|
设计模式 API 数据库
构建高效微服务架构:从理论到实践
【2月更文挑战第29天】 在现代软件开发领域,微服务架构已经成为一种流行的设计模式,它通过将大型应用程序拆分成一系列小型、独立的服务来提高系统的可维护性、扩展性和敏捷性。本文将深入探讨微服务的核心概念、设计原则以及如何在实际项目中实现和优化微服务架构。我们将从微服务的定义出发,讨论其与传统单体架构的区别,并分析微服务的优势与挑战。接着,文章将提供一套实践指南,包括服务划分、通信机制、数据一致性问题以及安全性考虑等方面,以指导开发者构建和维护一个高效的微服务系统。
|
14天前
|
Kubernetes 安全 Java
构建高效微服务架构:从理论到实践
【4月更文挑战第9天】 在当今快速迭代与竞争激烈的软件市场中,微服务架构以其灵活性、可扩展性及容错性,成为众多企业转型的首选。本文将深入探讨如何从零开始构建一个高效的微服务系统,覆盖从概念理解、设计原则、技术选型到部署维护的各个阶段。通过实际案例分析与最佳实践分享,旨在为后端工程师提供一套全面的微服务构建指南,帮助读者在面对复杂系统设计时能够做出明智的决策,并提升系统的可靠性与维护效率。
|
1月前
|
消息中间件 敏捷开发 运维
构建高效可靠的微服务架构:策略与实践
随着现代软件开发的复杂性增加,微服务架构逐渐成为企业解决大型应用系统分解、敏捷开发和持续部署问题的有效手段。本文深入探讨了构建一个高效且可靠的微服务架构的关键策略,包括服务的合理划分、通信机制的选择、数据一致性保障以及容错处理。通过分析这些策略在具体案例中的应用,我们旨在为开发者提供一套可行的微服务设计及实施指南。
131 6
|
1月前
|
Cloud Native 安全 持续交付
构建未来:云原生架构的演进与实践
【2月更文挑战第30天】 随着数字化转型的深入,企业对于信息技术的需求日益复杂化和动态化。传统的IT架构已难以满足快速迭代、灵活扩展及成本效率的双重要求。云原生技术作为解决这一矛盾的关键途径,通过容器化、微服务、持续集成/持续部署(CI/CD)等手段,实现了应用的快速开发、部署及运维。本文将探讨云原生架构的最新发展,分析其如何助力企业构建更加灵活、高效的业务系统,并结合实际案例,展示云原生转型过程中的最佳实践和面临的挑战。
|
7天前
|
消息中间件 运维 监控
现代化软件开发中的微服务架构设计与实践
本文将深入探讨现代化软件开发中微服务架构的设计原则和实践经验。通过分析微服务架构的优势、挑战以及常见的设计模式,结合实际案例,帮助开发者更好地理解如何构建可靠、可扩展、高效的微服务系统。
|
7天前
|
负载均衡 Java 开发者
细解微服务架构实践:如何使用Spring Cloud进行Java微服务治理
【4月更文挑战第17天】Spring Cloud是Java微服务治理的首选框架,整合了Eureka(服务发现)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、Zuul(API网关)和Config Server(配置中心)。通过Eureka实现服务注册与发现,Ribbon提供负载均衡,Hystrix实现熔断保护,Zuul作为API网关,Config Server集中管理配置。理解并运用Spring Cloud进行微服务治理是现代Java开发者的关键技能。
|
8天前
|
敏捷开发 监控 前端开发
深入理解自动化测试框架Selenium的架构与实践
【4月更文挑战第16天】 在现代软件开发过程中,自动化测试已成为确保产品质量和加快迭代速度的关键手段。Selenium作为一种广泛使用的自动化测试工具,其开源、跨平台的特性使得它成为业界的首选之一。本文旨在剖析Selenium的核心架构,并结合实际案例探讨其在复杂Web应用测试中的高效实践方法。通过详细解读Selenium组件间的交互机制以及如何优化测试脚本,我们希望为读者提供深入理解Selenium并有效运用于日常测试工作的参考。
14 1
|
11天前
|
Linux 数据安全/隐私保护
Linux基础与服务器架构综合小实践
【4月更文挑战第9天】Linux基础与服务器架构综合小实践
1234 8
|
20天前
|
消息中间件 监控 API
构建高性能微服务架构:从理论到实践
【4月更文挑战第4天】 在当今互联网应用的快速迭代和高并发需求下,传统的单体应用架构已不足以满足市场的灵活性与扩展性要求。微服务架构以其独立部署、弹性伸缩、技术多样性等优势,成为众多企业转型升级的首选方案。本文将深入探讨如何构建一个高性能的微服务系统,涵盖关键组件的选择、系统设计的考量以及性能优化的策略,旨在为开发者和架构师提供一套实用的指导思路和具体实践步骤。