当前位置: 首页 > 科技观察

浅谈Flux架构与Redux实践

时间:2023-03-17 12:18:42 科技观察

Flux概述Flux是Facebook用来构建用户端Web应用程序的架构。与其他形式化的框架相比,它更像是一种架构思想,用于管理和控制应用程序中的数据流。这里的应用程序中的数据包括但不限于来自服务器的数据页面中视图的一些状态(如比如面板是展开还是关闭),本地暂存的需要持久化到服务器的数据等等。好了,说了这么多好像还是一头雾水,不慌不忙,下面看展开。在MVC讲Flux之前,我们先看看以前传统的MVC架构和前端的一些问题,再想想Flux带来的变化。MVC(Model-View-Controller)最先出现在后端,通过简化应用程序的复杂性,使程序更加直观易维护。在后端程序MVC中,View可以看作是数据的呈现,Model是数据的模型,Controller是程序的流程控制。现在假设有这样一个场景,用户想要查看他的个人资料页面,可能会有这样一个过程:点击页面上的个人资料按钮,然后是一个HTTP请求(/profile?username=jiavan)=>Controller收到这个请求并获取请求内容username=jiavan然后通知Model需要jiavan的数据=>Model返回jiavan的数据=>Controller获取数据返回一个新的view,看流程:现在又是一个场景前端:在MenuItem中切换,当前选中的Item颜色与其他颜色不同,底部显示对应Item的内容。一般我们会定义一个cssclass作为当前选中Item的样式。当用户单击Item_A以将突出显示的类添加到单击的元素时,其他同级元素将删除该样式。这里的事件响应函数是Controller,这里我们会处理样式修改逻辑,更新Model数据,然后新建Data和样式重新渲染界面。VC<->M这种形式在关系比较简单的时候比较清晰,容易控制,但是在复杂的页面上这样的模式可能会变得很混乱:它变得混乱是因为很多视图都有修改多个模型的能力。这里的单个修改行为可以称为一个Action。Action的产生可能是用户行为,也可能是Ajax请求需要渲染新的界面。对比上面后端传统的MVC模式,可以发现:后端的Action是一个URL请求,在前端可能是一个事件;后端的Action处理集中在Controller,而前端是分散的。那么是不是可以把前端(事件回调/Ajax)中所有修改状态的行为抽象成一个Action描述,然后交付到一个地方,Reducers,进行原子处理,然后Reducer修改唯一的整个应用中state树就是Store,最好通过state->view机制重新渲染?上面提到的Flux数据流框架中的几个概念,对于Flux已经有了初步的了解,那么我们进入正题吧。相信了解Flux的人应该都看过下面这张著名的数据流图:Action可以看作是修改Store的行为抽象;Dispatcher管理着应用的数据流,可以看作是Action到Store的分发者;Store管理着整个应用的状态和逻辑,类似于MVC中的Model。所以Flux可以看作是对传统MVC的改进而不是颠覆。第一次看到Flux的时候,其实还挺迷茫的,但是在看到和使用Redux之后,真的有一种非常神奇的感觉。根据Redux官方的描述,ReduxisapredictablestatecontainerforJavaScriptapps.,其中predictable和statecontainer体现了它的作用。那么如何理解可预测性呢?这里会有一些函数式编程的思想。在Redux中,reducer函数是一个纯函数,相同的输入一定会有一致的输出。因此,如果输入的状态是确定的,那么reducer函数的输出状态必须是可预测的,因为它只会执行简单的计算以确保正确的输出。什么是状态容器?就是说Redux有一个专门管理状态的地方,就是Store,一般来说是唯一的。应用中所有状态组成的状态树就是Store。Redux由Flux演变而来,但受到Elm的启发,避免了Flux的复杂性,让我们看看它的数据流:与Flux架构不同,Redux中没有调度器概念,并且Redux假设你永远不会更改你的数据,你应该返回reducer中的新对象作为应用程序的新状态。但是它们都可以用(state,action)=>newState来表达自己的核心思想,所以Redux可以看作是Flux思想的一种实现,只是在细节上会有一些区别。重要概念应用程序中的所有状态都以对象树的形式存储在单个存储中;改变store的唯一方法是触发一个action,action是action行为的抽象;为了描述一个动作如何改变状态树,你需要写一个reducer函数。这里需要说明的是reducer函数,它应该是一个纯函数,不应该有副作用,不应该有API调用,Date.now()或随机获取等不稳定操作,并且应该保证相同的输入reducer计算结果应该是一个一致的输出,它只是做纯计算。编写reducer函数也是Redux的重要组成部分。它的形式如下:functiontestReducer(state,action){switch(action.type){caseACTION_TYPE://calc...returnnewState;default:returnstate;}returnnewState;}state是不可修改的,所以返回的新状态应该是基于关于输入状态副本的修改,不是直接修改状态后的返回。原则1.单一数据源,store整个应用的状态存储在一棵Object树中,而这棵Object树只存在于唯一的Store中;2.状态为只读。改变状态的唯一方法是触发一个动作,动作是对已经发生的事情的抽象描述。简单地说,它将行为抽象为一个对象。比如删除一条记录的动作可以抽象理解为:{type:'DELETE_ITEM',index:1,}3.使用纯函数实现状态合并操作,reducer传入需要修改的状态以及告诉reducer如何修改状态action的消息,reducer会在action规则对应的操作后返回新的state。reducer(state,action)=>newstate数据流严格的单向数据流是Redux设计的核心Redux应用数据的生命周期遵循以下4个步骤:调用store.dispatch(action),可以在任何地方进行;Reduxstore调用传入的reducer函数,传入当前状态树和action。reducer是一个纯函数,只用来计算下一个状态,应该是完全可以预测的,同样的输入一定有同样的输出,没有副作用的操作,比如API调用或者路由跳转,这些应该是之前生成的派遣;rootreducer将多个sub-reducer的输出组合成一个状态树;Reduxstore保存了根reducer返回的完整状态树。新的状态树就是应用程序的下一个状态,现在可以根据新的状态树来渲染UI了。Redux实践我们用一个非常简单的计数器demo来梳理一下Redux的数据流向。0x00。创建动作动作实际上是一个普通的对象,只是对行为的抽象描述。这里我们可以描述加一个数为:{type:INCREMENT,//动作的抽象描述数,//动作携带的数据}更多的时候我们会通过动作生成函数得到一个动作:functionincrementCreator(number){返回{type:INCREMENT,number,};}0x01。在整个Redux中创建一个reducer函数reducer作为action的处理中心,接收状态和action并修改数据,返回应用的下一个状态。functioncountReducer(state,action){switch(action.type){caseINCREMENT:returnObject.assign({},{counter:state.counter+action.number,});caseDECREMENT:returnObject.assign({},{counter:state.状态复制对象,不直接修改输入状态。0x02。创建一个唯一的store将Redux中的createStore方法传递给reducer函数来创建整个应用的store。conststore=createStore(countReducer);0x03。通过store的dispatch方法修改状态发起一个动作。store.dispatch(incrementCreator(5));store.dispatch(decrementCreator(4));完成demoimport{createStore}from'redux';//actionsconstINCREMENT='INCREMENT';constDECREMENT='DECREMENT';//actionCreator,可以查看创建action的语法糖functionincrementCreator(number){return{type:INCREMENT,number,};}functiondecrementCreator(number){return{type:DECREMENT,number,};}//初始化状态constinitialState={counter:0,};//reducers函数,注意returnstate,防止action匹配不上时state丢失state.counter+action.number,});caseDECREMENT:returnObject.assign({},{counter:state.counter-action.number,});default:returnstate;}}//创建storeconststore=createStore(countReducer);//订阅store修改constunsubscribe=store.subscribe(functionlog(){console.log(store.getState());});//改变statestore.dispatch(incrementCreator(5));//Object{counter:5}store.dispatch(decrementCreator(4));//Object{counter:1}//取消订阅unsubs屏幕();