1、基于React的Flux架构@H_403_1@
React 是基于View层的前端库,其核心是使用组件定义界面。React是组件化和状态机的思想,对于组件与组件、组件与数据模型之间的通信,页面的渲染,需要一套机制来组织整个应用的状态。Flux即是这样一种用来构建React的应用程序加架构,最大的特点是单向数据流,分层解耦。比较适用于大型项目。基本框架如下:
flux包括了四个东西:
view: 视图 controller调用action行为函数,实现change回调
action:动作action在dispatcher中的注册
dispatcher:派发器调用store中数据更新以及change 提交功能函数
store:数据层数据更新以及事件接口回调
应用共用一个派发器,一般View的数据变化主要有两个方面:来自服务器端的数据和用户交互数据。对于用户交互的数据,单向数据流的主要流程是:用户交互数据在View Controller中将数据交互封装成Action,并把该Action通过dispatch()函数携带actionType加入到在派发器中;派发器通过register()函数,将不同类型的action分发到不同的数据层进行处理(调用数据层中的不同函数),并提交事件(emit);数据层Store需要编写不同的数据处理函数,来进行本地数据存储的更新(一般这个函数的编写与Action对应),Store需要在变动后向View发送"change"事件,因此必须实现事件接口(继承EventCommitter.prototype,调用Store.emit()和store.on()实现)。
注:1、viewcontroller 保存状态,转发给子组件,这样保证子组件不含任何状态,从而进行组件复用。
2、事件接口的实现在Store中,Dispatcher中更新Store,提交change event;Controller view中监听change event,从Store中拉取数据, 传给子组件。
3、基于React的Demo:https://github.com/xianyulaodi/myFluxTest
2、Flux在RN中的应用@H_403_1@
Flux当初是针对React提出的,RN是基于React的衍生品,以下针对RN进行Flux的应用研究。
Demo流程图:
demo地址:https://github.com/saralalala/flux-RN
3、Flux主流框架Redux(使用React-Redux库)@H_403_1@
Redux是Flux的一种框架实现,Redux 放弃了 event emitters(事件发送器),转而使用纯 reducer。使用更方便简洁:
(1)发起action:实现action在dispatcher中的注册(把action创建函数传到到dispatch()方法上或者创建被绑定的action函数)
function addTodo(text) { return type: ADD_TODO, text }}1)action创建函数传到到dispatch()方法上: dispatch(addTodo(text)) 2)创建一个被绑定的action创建函数: const boundAddTodo = (text) => dispatch(addTodo(text));boundAddTodo(text);
(2)reducer:纯函数,接收旧的state和action,返回新的state(注意不要修改旧的state值)
(prevIoUsState, action) => newState
reducee根据业务逻辑可以有很多个,最终传给Store的只有一个,可以通过combineReduces()
合成reducer,生成一个函数。这个函数根据reducer的 key 来筛选出 state 中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象,用来创建。
(3)store: 确定state对象结构
1)一个应用中只有唯一一个Store;
2)Store以树的形式保存了整个应用的State,本质上是一个对象。主要职责:
- 维持应用的 state;
- 提供
getState()
方法获取 state;
- 提供
dispatch(action)
方法更新 state(发布);
getState()
方法获取 state;dispatch(action)
方法更新 state(发布);- 通过
subscribe(listener)
注册监听器,订阅Store树的监听; replaceReducer()
动态替换reducer函数
3)建议格式:todosById:{id->todo}
和todos:array<id>
redux数据流程源码分析:
当state数据变化时,会触发所有订阅了该事件的监听器,在dispatch中循环执行监听器回调函数,从而在回调函数中进行相应处理.
1、事件的订阅通过subscribe(listener)执行
2、事件的触发在dispatch(action)中
源码:
(1)store.subscribe()
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
var isSubscribed = true;
ensureCanMutateNextListeners()
nextListeners.push(listener);
//返回接口,正好利用闭包可以保持对相应Listener的访问
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
var index = nextListeners.indexOf(listener)
nextListeners.splice(index,1)
}
}
(2)store.dispatch(action)
/**
* 唯一改变state的接口
* 生成nextState同时通知观察者
* 每次dispatch都会执行state的观察者
*
* return action这样设计比较好的一点是方便扩展中间件!!!
*/
function dispatch(action) {
/**
* 错误处理,可以跳过
*/
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
/**
* 将当前状态和action作为参数传值给reducer,生成下个状态
*/
try {
isDispatching = true
currentState = currentReducer(currentState,action)
} finally {
isDispatching = false
}
var listeners = currentListeners = nextListeners
for (var i = 0; i < listeners.length; i++) {
/**
* 遍历subscribe的观察者
*/
listeners[i]()
}
return action
}
(4)在React中的应用:需要React-Redux 提供API(Provider/Connect)
Provider和Connect是React-Flux提供的接口,负责把React Component 和 Redux store 结合起来:
Provider 把可以将从createStore
返回的store放入context中,使子集可以获取到store并进行操作;
<Provider store={store}>{() => <App />}</Provider>
connect的两个可选参数,前两个
-
[mapStateToProps(state,[ownProps]): stateProps]
: 第一个可选参数是一个函数,只有指定了这个参数,这个关联(connected) - 组件才会监听 Redux Store 的更新,每次更新都会调用
mapStateToProps
这个函数,返回一个字面量对象将会合并到组件的 -
props
属性。ownProps
是可选的第二个参数,它是传递给组件的props
,当组件获取到新的props
时,ownProps
- 都会拿到这个值并且执行
mapStateToProps
这个函数。 -
[mapDispatchProps(dispatch,[ownProps]): dispatchProps]
: 这个函数用来指定如何传递dispatch
给组件,在这个函数里面 - 直接 dispatch action creator,返回一个字面量对象将会合并到组件的
props
属性,这样关联组件可以直接通过props
调用 - 到
action
, Redux 提供了一个bindActionCreators()
辅助函数来简化这种写法。 如果省略这个参数,默认直接把 -
dispatch
作为props
传入。ownProps
作用同上。
在 redux 中,没有与 redux 有直接关联的组件称为木偶组件,不理外面纷纷扰扰,只知道自己拥有了 state 及 具备操作 state 数据的 actions 方法。
当木偶组件使用 actions 方法,更新了 store.state 的数据时,将会触发 store 中的 subscribe 所注册的函数。而其中一个注册函数,就在 Connect 组件中静默注册了。
我们把connect返回的函数叫做Connector,它返回的是内部的一个叫Connect的组件,它在包装原有组件的基础上,还在内部监听了Redux的store的变化,
为了让被它包装的组件可以响应store的变化:
trySubscribe() { if (shouldSubscribe && !this.unsubscribe) { this.unsubscribe = this.store.subscribe(::this.handleChange) this.handleChange() }}
handleChange () { this.setState({ storeState: .store.getState() })}
但是通常,我们connect的是某个Container组件,它并不承载所有App state,然而我们的handler是响应所有state变化的,于是我们需要优化的是:当storeState变化的时候,仅在我们真正依赖那部分state变化时,才重新render相应的React组件,那么什么是我们真正依赖的部分?就是通过mapStateToProps
和mapDispatchToProps
得到的。
具体优化的方式就是在shouldComponentUpdate
中做检查,如果只有在组件自身的props改变,或者mapStateToProps
的结果改变,或者是mapDispatchToProps
的结果改变时shouldComponentUpdate
才会返回true,检查的方式是进行shallowEqual的比较。
this . store . subscribe ( this . handleChange . bind ( this ) ) ;
即当 actions 更改了 state 时,会调用注册函数 handleChange。从而进行 “阿米诺骨牌式” 的函数执行连锁反应。更新了 state,并使用新的数据重新 render 组件。
实际上是为智能组件(传入 connect 的组件)传入新的 props,因为各个子元素是通过引用父级组件的 props,所以将进行一级一级的差异数据更新(PureRenderMixin
),最终效果就是页面更新了。
实际上,这里与简单的发布订阅模式类似。使用 store.subscribe(cb); 来订阅一个回调函数,子组件进行 action 操作 store.state 时进行发布,执行了回调函数。
通过connect 获得store中的state,并转化成Component的props来使用。(以下为引用Wang Namelos在知乎的回答)
1. React有props和state: props意味着父级分发下来的属性,state意味着组件内部可以自行管理的状态,并且整个React没有数据向上回溯的能力,也就是说数据只能单向向下分发,或者自行内部消化。
理解这个是理解React和Redux的前提。
2. 一般构建的React组件内部可能是一个完整的应用,它自己工作良好,你可以通过属性作为API控制它。但是更多的时候发现React根本无法让两个组件互相交流,使用对方的数据。
然后这时候不通过DOM沟通(也就是React体制内)解决的唯一办法就是提升state,将state放到共有的父组件中来管理,再作为props分发回子组件。
3. 子组件改变父组件state的办法只能是通过onClick触发父组件声明好的回调,也就是父组件提前声明好函数或方法作为契约描述自己的state将如何变化,再将它同样作为属性交给子组件使用。
这样就出现了一个模式:数据总是单向从顶层向下分发的,但是只有子组件回调在概念上可以回到state顶层影响数据。这样state一定程度上是响应式的。
4. 为了面临所有可能的扩展问题,最容易想到的办法就是把所有state集中放到所有组件顶层,然后分发给所有组件。
5. 为了有更好的state管理,就需要一个库来作为更专业的顶层state分发给所有React应用,这就是Redux。让我们回来看看重现上面结构的需求:
a. 需要回调通知state (等同于回调参数) -> action
b. 需要根据回调处理 (等同于父级方法) -> reducer
c. 需要state (等同于总状态) -> store
对Redux来说只有这三个要素:
a. action是纯声明式的数据结构,只提供事件的所有要素,不提供逻辑。
b. reducer是一个匹配函数,action的发送是全局的:所有的reducer都可以捕捉到并匹配与自己相关与否,相关就拿走action中的要素进行逻辑处理,修改store中的状态,不相关就不对state做处理原样返回。
c. store负责存储状态并可以被react api回调,发布action.
当然一般不会直接把两个库拿来用,还有一个binding叫react-redux,提供一个Provider和connect。很多人其实看懂了redux卡在这里。
a. Provider是一个普通组件,可以作为顶层app的分发点,它只需要store属性就可以了。它会将state分发给所有被connect的组件,不管它在哪里,被嵌套多少层。
b. connect是真正的重点,它是一个科里化函数,意思是先接受两个参数(数据绑定mapStateToProps和事件绑定mapDispatchToProps),再接受一个参数(将要绑定的组件本身):
mapStateToProps:构建好Redux系统的时候,它会被自动初始化,但是你的React组件并不知道它的存在,因此你需要分拣出你需要的Redux状态,所以你需要绑定一个函数,它的参数是state,简单返回你关心的几个值。
mapDispatchToProps:声明好的action作为回调,也可以被注入到组件里,就是通过这个函数,它的参数是dispatch,通过redux的辅助方法bindActionCreator绑定所有action以及参数的dispatch,就可以作为属性在组件里面作为函数简单使用了,不需要手动dispatch。这个mapDispatchToProps是可选的,如果不传这个参数redux会简单把dispatch作为属性注入给组件,可以手动当做store.dispatch使用。这也是为什么要科里化的原因。
demo地址:demo地址:https://github.com/saralalala/redux-RN
5、参考文章:@H_403_1@
1)http://www.ruanyifeng.com/blog/2015/03/react.html;廖雪峰React
2)http://www.cnblogs.com/xianyulaodi/p/5358315.html;Flux学习笔记
3)http://cn.redux.js.org/docs/introduction/ThreePrinciples.html ; Redux中文官网
4)http://www.jianshu.com/p/09956d82bca6;Redux解析
5)http://www.zhihu.com/question/41312576?sort=created ;