一.定位
一种模式,用来强化单向数据流(unidirectional data flow)
二.作用
剥离数据层,让数据可预测(React让UI可预测,Flux让数据可预测)
具体做法:
用显式数据,不用衍生数据(先声明后使用,不临时造数据)
分离数据和视图状态(把数据层抽出来)
避免级联更新带来的级联影响(M与V之间互相影响,数据流不清楚)
作用:
提升数据一致性
易于精确定位bug
便于单元测试
三.结构
产生action 传递action update state
view交互 -----------> dispatcher -----------> stores --------------> views
其中dispatcher
全局只有一个,store
可以有多个。dispatcher
只负责分发/传递action
,action
到具体state
变化之间的映射由store
维护,所以store
不是单纯的状态集model
,还包含根据action
更新state
的逻辑。再往后就是state
到view
的联系,与数据绑定的具体实现有关,比如React里通过触发事件来通知更新(隐式setState()
)
业务逻辑大多在store
里,另一小部分交互相关的、异步操作相关的在view
(比如React组件)里
业务中经常有级联更新,比如交互操作把一条消息标为已读,要更新消息列表中该条消息的展示样式,还要把未读消息数量减一,级联更新让单向数据流变得不再清晰。Flux通过约束必须在顶层触发action
来避免这种情况,一次view
交互触发一组action
(把级联action
打平,并把级联关系收在顶层,与交互操作直接相关)。而不是一次view
交互触发一个大action
,大action
触发下面的级联action
由store
来完成控制反转,store
不提供setXXX()
来允许外部影响内部state
,唯一的方式是通过在dispatcher
上注册的回调拿到外部数据,自己更新内部state
,保持清楚的关注点分离
单dispatcher
中心枢纽,所有数据流都要过这里,有一张回调注册表,与各store
建立联系。dispatcher本身只负责把action
传递给所有store
,每个store
在dispatcher
注册自己并提供一个回调,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集,在逻辑上表示一块单一功能
store
在dispatcher
上注册的回调接受一个action
参数,store
里面是一个switch
语句,根据action
的type
分发给具体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
的过程,内部维持store
与action
的联系(通过action
的type
)
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
,没什么特别的