Flux

一.定位

一种模式,用来强化单向数据流(unidirectional data flow)

二.作用

剥离数据层,让数据可预测(React让UI可预测,Flux让数据可预测)

具体做法:

  • 用显式数据,不用衍生数据(先声明后使用,不临时造数据)

  • 分离数据和视图状态(把数据层抽出来)

  • 避免级联更新带来的级联影响(M与V之间互相影响,数据流不清楚)

作用:

  • 提升数据一致性

  • 易于精确定位bug

  • 便于单元测试

三.结构

         产生action               传递action           update state
view交互 -----------> dispatcher -----------> stores --------------> views

其中dispatcher全局只有一个,store可以有多个。dispatcher只负责分发/传递actionaction到具体state变化之间的映射由store维护,所以store不是单纯的状态集model,还包含根据action更新state的逻辑。再往后就是stateview的联系,与数据绑定的具体实现有关,比如React里通过触发事件来通知更新(隐式setState()

业务逻辑大多在store里,另一小部分交互相关的、异步操作相关的在view(比如React组件)里

业务中经常有级联更新,比如交互操作把一条消息标为已读,要更新消息列表中该条消息的展示样式,还要把未读消息数量减一,级联更新让单向数据流变得不再清晰。Flux通过约束必须在顶层触发action来避免这种情况,一次view交互触发一组action(把级联action打平,并把级联关系收在顶层,与交互操作直接相关)。而不是一次view交互触发一个大action,大action触发下面的级联action

store来完成控制反转,store不提供setXXX()来允许外部影响内部state,唯一的方式是通过在dispatcher上注册的回调拿到外部数据,自己更新内部state,保持清楚的关注点分离

flux-simple-f8-diagram-explained

flux-simple-f8-diagram-explained

单dispatcher

中心枢纽,所有数据流都要过这里,有一张回调注册表,与各store建立联系。dispatcher本身只负责把action传递给所有store,每个storedispatcher注册自己并提供一个回调,dispatcher收到action后,所有已注册的store都将通过各自的回调拿到action及其携带的数据

应用规模较大的时候,dispatcher会变得复杂一些,还要管理各store之间的依赖关系(按顺序调用各store注册的回调),store可以通过显示声明等待其它store更新完成后再更新自己

一堆store

包含应用状态和逻辑,角色相当于MVC里的重M,但管理一堆state,而不像ORM里model代表一条数据记录,与Backbone里的collection也不同,只是简单地管理一组ORM风格的对象

一个store负责管理应用某块功能对应的内部状态,也就是说,store不是按具体数据模型(ORM model),或者类型(Backbone collection)来分的,而是按业务功能划分。比如ImageStore负责记录一组图片的状态,TodoStore负责记录一组to-do item,这样,store在数据上表示model集,在逻辑上表示一块单一功能

storedispatcher上注册的回调接受一个action参数,store里面是一个switch语句,根据actiontype分发给具体state更新方法,store更新完毕后,通过广播事件来告诉view某些状态变了,对应的view取新的状态更新自己

一堆view

一些特殊的view监听来自自己依赖的store的广播事件,这些叫view叫controller-view,含有从store取数据及向下传递给后代view的逻辑,一个controller-view通常对应页面上的一块逻辑内容,像view的逻辑分组一样

controller-view接到来自store的事件后,先通过store暴露的getter取新数据,然后调用自己的setState()或者forceUpdate(),触发render()render()触发后代的render()

通常把一大块state向下传递,下面各取所需,是为了减少需要管理的状态(不做细粒度状态切分)。相对于顶层controller从外部更新状态,这样能保持后代的功能尽量纯净

一堆action

一般用工具方法来包装action的生成、注册到store的过程,内部维持storeaction的联系(通过actiontype

action也可能来自别处,比如服务端,数据初始化时,服务返回错误码或者服务数据更新了,通过触发action来同步视图

四.特点

强制同步

action分发/传递和store内部更新state都是同步的,异步操作的话,完成的时候手动触发action,整个机制不帮忙管理异步操作

让应用的信息流非常明确,bug场景对应的state向上追溯到store,到对应action,再到view层触发action的点,过程中所有环节都是同步的,那么action对应的state就是可预测的,不存在时序上的意外

控制反转(IoC)

store自己内部更新state,而不是从外部更新,这样其它部分都不需要知道具体的state变化,状态变化只与store有关。而store只接收action,想对store做单元测试的话,只需要给一个初态,再丢过来一个action,然后看终态是否符合预期即可

语义化的action

store要根据action更新state,这样一个action就相当于一组state更新操作的名字,有了语义含义,action不知道怎样更新状态,但描述了预期结果,是相对稳定的(很少需要修改action,因为仅描述应用的某项功能),比如MARK_THREAD_READ希望把某条消息置为已读

额外的语义信息有利于追踪状态变化,通过调试工具就能让状态变化可追踪,比如Redux DevTools

没有级联action

不允许一个action触发另一个action,以避免级联更新带来的调试复杂度,所以action是“原子级”的,没有复杂的层级关系

五.约定

最佳实践部分,也就是Flux的道德约束

store

  • 缓存数据

  • 只暴露用来访问数据的getter,不给setter

  • 对来自dispatcher的特定action作出响应

  • 任何数据变化时都触发change事件

  • 只在dispatch过程中才触发change事件

维护内部状态,且只在内部更新状态,关注特定action,数据变化时无理由触发change,其它时候不触发,除非是dispatcher引发的

action

  • 描述用户行为,而不是setter(比如应该是select-page而不是set-page-id

container

  • 用来控制view的React组件

  • 基本职能是收集来自store的信息,存到自己的state

  • 不含props和UI逻辑

其实就是controller-view,与普通view的区别如上所述

view

  • 由container控制的React组件

  • 含有UI和渲染逻辑

  • 接收所有信息和回调作为props

普通的view,没什么特别的

参考资料

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code