配色: 字号:
Redux原理(一):Store实现分析
2016-09-12 | 阅:  转:  |  分享 
  
Redux原理(一):Store实现分析

Redux如何管理state

注册storetree

1、Redux通过全局唯一的store对象管理项目中的state
varstore=createStore(reducer,initialState);
2、可以通过store注册listener,注册的listener会在storetree每次变更后执行
store.subscribe(function(){
console.log("statechange");
});
如何更新storetree

1、store调用dispatch,通过action把变更的信息传递给reducer
varaction={type:''add''};
store.dispatch(action);
2、store根据action携带type在reducer中查询变更具体要执行的方法,执行后返回新的state
exportdefault(state=initialState,action)=>{
switch(action.type){
case''add'':
return{
count:state.count+1
}
break;
default:
break;
}
}
3、reducer执行后返回的新状态会更新到storetree中,触发由store.subscribe()注册的所有listener
Store实现

主要方法:
createStore
combineReducers
bindActionCreators
applyMiddleWare
compose
createStore源码分析

查看完整createStore请戳这里
createStore方法用来注册一个store,返回值为包含了若干方法的对象,方法体如下:
exportvarActionTypes={
INIT:''@@redux/INIT''
}

exportdefaultfunctioncreateStore(){

functiongetState(){}

functiondispatch(){}

functionsubscribe(){}

functionreplaceReducer(){}

dispatch({type:ActionTypes.INIT})

return{
dispatch,
subscribe,
getState,
replaceReducer
}
}
下面逐个代码段分析功能
createStore完整函数声明如下:
createStore(
reducer:(state,action)=>nextState,
preloadedState:any,
enhancer:(store)=>nextStore
)=>{
getState:()=>any,
subscribe:(listener:()=>any)=>any,
dispatch:(action:{type:""})=>{type:""},
replaceReducer:(nextReducer:(state,action)=>nextState)=>void
}
可以看出整个函数是一个闭包结构。参数有三个,返回值公开出若干方法
dispatch:分发action
subscribe:注册listener,监听state变化
getState:读取storetree中所有state
replaceReucer:替换reducer,改变state更新逻辑
当然,createStore内部处理了其重载形式,即:可以不传preloadedState
createStore(
reducer:(state,action)=>nextState,
enhancer:(store)=>nextStore
)
参数:
reducer:reducer必须是一个function类型,此方法根据action.type更新state
preloadedState:storetree初始值
enhancer:enhancer通过添加middleware,增强store功能
前置操作


进入createSore首先执行如下操作:
[40-43]用于支持两种参数列表形式createStore(reducer,preloadedState,enhancer)和createStore(reducer,enhancer)
[45-48][53-55]校验reducer和enhancer的类型(必须为function)
重点分析下50行:
returnenhancer(createStore)(reducer,preloadedState)
本语句执行了外部传入的enhancer,接收旧createStore,返回一个新createStore并执行,此过程形成一次递归;
那么递归什么时候停止呢?
可以看到,新createStore执行时,仅有reducer和preloadedState两个参数,再次运行到45行时,不会进入if条件故不会再形成第二次递归,此时递归停止;
理论上,createStore仅被增强了一次,那如果希望对其进行多次增强该怎么办呢?
Redux提供了compose和applyMiddleWare方法,用来在Store上注册中间件,由此来实现多次增强。
getState()

getState方法比较简单,直接返回当前storetree状态

[57-61]定义了createStore内部要用到的全局变量。其中currentReducer、currentState声明当前reducer方法集合和storetree状态,初始值为外部传入的createStore参数,currentListeners和nextListeners定义了存放store变化时要执行响应函数的数组集合
subscribe()

Redux采用了观察者模式,store内部维护listener数组,用于存储所有通过store.subscrib注册的listener,store.subscrib返回unsubscrib方法,用于注销当前listener;当storetree更新后,依次执行数组中的listener
具体代码如下:

dispatch()

dispatch方法主要完成两件事:
1、根据action查询reducer中变更state的方法,更新storetree
2、变更storetree后,依次执行listener中所有响应函数

[168-173]通过currentReducer和action,更新当前的storetree
[175-181]当statetree变更后,依次执行所有注册的listener
有个问题需要注意:
方法中使用了全局定义的isDispatching用于给变更中的storetree加锁;即:只有当本次storetree变更完毕后,才允许执行下一次变更,避免storetree响应多个变更时,结果不同步的问题;但事实上,这种写法也决定了,目前的storetree只能响应同步变更(异步变更需要通过添加中间件实现)
replaceReducer()

replaceReducer用于替换操作storetree中state的方式

整个方法代码量不多,从外部接收新的reducer方法后,替换掉内部旧的ruducer。
需要注意一下199行的dispatch方法,这一行主动触发了一次变更。由于每次dispatch执行后,redux都会执行reducer或子reducer方法(如果使用了combineReducers),所以这一行的作用就是在初始化storetree中所有的state节点。
小结

以上就是整个createStore方法的主要实现过程,其中dispatch方法为控制整个storetree变更的核心方法。触发storetree变更的方式只有一个,就是dispatch一个action
combineReducers源码分析

为什么需要combineReducers

结合上面storetree变更的过程,我们可以看到,真正导致变更的核心代码就是:
currentState=currentReducer(currentState,action)
试想,若整个项目只通过一个reducer方法维护整个storetree,随着项目功能和复杂度的增加,我们需要维护的storetree层级也会越来越深,当我们需要变更一个处于storetree底层的state,reducer中的变更逻辑会十分复杂且臃肿。
而combineReducers存在的目的就是解决了整个storetree中state与reducer一对一设置的问题。我们可以根据项目的需要,定义多个子reducer方法,每个子reducer仅维护整个storetree中的一部分state,通过combineReducers将子reducer合并为一层。这样我们就可以根据实际需要,将整个storetree拆分成更细小的部分,分开维护。
代码实现

combineReducers完整代码请戳这里
整个函数体结构如下:
combineReducers(Java-Spring一主多从、多主多从数据库配置

待会苹果要开发布会

我写完这篇文章就准备去看发布会了,因为我买了好几包瓜子和啤酒。由于苹果的保密做的越来越差劲,该曝光的信息差不多全部曝光了,我们这种熬夜看发布会的只不过是让这些信息更加真实,或者说是一种习惯了吧,因为每次苹果和锤子的发布会都必不可少的守着电脑看。

你要问我最期待什么新产品?可能是新款的MacBookPro吧。因为新款iPhone从曝光信息看摄像头依然凸起、白带也是存在、ID设计依然如此,哎、苹果在走下坡路了;由于我的笔记本是大学时期买的,到现在已经完美服役四五年了,虽然还是快的飞起,但是我就想换个新的。也不知道发了工资能不能买得起。伤感...

特么的,看完后,发现并没有新款的MacBookPro...

废话不多说,直接上配置

一、新建jdbc.properties配置文件

master.jdbc.driverClassName=com.mysql.jdbc.Driver
master.jdbc.url=jdbc:mysql://127.0.0.1:3306/springdemo?useUnicode=true&characterEncoding=UTF-8
master.jdbc.username=root
master.jdbc.password=123456

slave.jdbc.driverClassName=com.mysql.jdbc.Driver
slave.jdbc.url=jdbc:mysql://127.0.0.1:3306/springdemo?useUnicode=true&characterEncoding=UTF-8
slave.jdbc.username=read
slave.jdbc.password=123456
配置文件的作用大家都清楚了,是因为我们可以在applicationContext.xml文件中以${master.jdbc.url}的形式读取内容,配置文件一般在/src/目录下。

二、配置applicationContext.xml


class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">


classpath:global.properties
classpath:jdbc.properties





init-method="init"destroy-method="close">

${master.jdbc.driverClassName}


${master.jdbc.url}


${master.jdbc.username}


${master.jdbc.password}


...



init-method="init"destroy-method="close">

${slave.jdbc.driverClassName}


${slave.jdbc.url}


${slave.jdbc.username}


${slave.jdbc.password}


...

















class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>





...
上面配置我省略了druid数据连接池的一些配置和service层事务处理,文件并不完整,具体文件示例见GitHub:https://github.com/mafly/SpringDemo/blob/master/WebContent/WEB-INF/applicationContext.xml

如何使用阿?

我直接贴一个具体的Service层代码你就完全懂了。

publicinterfaceUserBasisService{

/
保存
@paramentity
@return
/
@DataSource
publiclongSave(UserBasisentity);

/
删除
@paramID
@return
/
@DataSource
publicBooleanDelete(longID);

/
获取信息
@paramID
@return
/
@DataSource(DataSourceType.Slave)
publicUserBasisgetEntity(longID);

/
根据条件获取数据条数
@return
/
@DataSource(DataSourceType.Slave)
publicintgetListCount(MapwhereMap);

/
获取所有
@return
/
@DataSource(DataSourceType.Slave)
publicListgetList();
}
就是直接打标签的形式切换就可以了,这里需要注意的有两点,也是我们曾经踩过的坑:
1.注意事务是在何处!就是说,要在一个事务开始之前做数据源的切换。
2.不要又想写又想读!还是在一个事务内不要有读的方法又有写的方法。

到这里读写分离和主从动态切换数据源的配置以及使用就完整了。接下来思考:我们是不是有时候项目都是要一主多从、多主多从?

一主多从、多主多从

一主多从的架构很多人都在使用,美其名是减小读数据的压力,我还是保留上一篇文章的看法,可能数据安全是最大的作用,再有就是你有数据报表和数据统计系统,使用一主多从架构可以避免生产服务器的访问压力过大。
配置一主多从架构其实根据我们上面的设计就很简单了,只需要在applicationContext.xml文件中配置多个从库数据源就可以,然后当你读取从库时,可根据你现有的从库数来进行一些负载均衡算法的切换,我这里就不再演示了。

多主多从是什么鬼?首先我需要说明的是多主多从这里并不是指的同一个业务数据库,是指不同的业务数据库,就是大家所说的「分库分表」中的分库,就是说我们一个项目中分出了不同的业务数据库,然后这些不同的数据库也可以有多个从库,可不是一个业务数据库有多个主库、多个从库,据我所知,MySql的复制也是不建议这么做的。
了解清楚概念后,我们目标就清晰了,其实根据我们的数据源切换架构,再接着配置多个数据源就可以了。其实也是这么简单的意思,比如:项目中有个金币系统,用户完成我们期望的操作就会给他相应的金币,他可以用金币兑换我们商城里的物品。这时候,其实我们就应该有个金币库了,不要再去和业务共用一个数据库,所以,这时候就会用到我们「多主多从」的架构了。

reducers:Object
)=>reducer(
state:any,
action:{type:""}
)
参数reducers是一个Object对象,其中包含所有待合并的子reducer方法
返回值是合并后的reducer方法,在执行此方法时,会在已合并的所有子reducer中查询要执行的reducer,并执行,变更其对应的state片段。
下面逐个代码段分析具体实现:

以上这部分主要用于规范化存储子ruducer的reducers对象
[103-115]过滤掉reducers对象中所有非function类型的reducer,合法的结果保存在finalReducers对象中
[116-127]通过assertReducerSanity方法校验所有子reducer的初始值和执行后结果是否为空,是则提示错误。

以上这段代码为combineReducers的核心代码,其返回一个function,用于查询真正要变更的state片段
[141-154]遍历规范化后的finalReducers,获取到当前key对应的子reducer和子state,执行reducer得到当前state片段更新后的状态,并更新到整个storetree中。
其中129行中的state,应该是整个storetree对应的state,首次获取previousStateForKey时,值可能为undefined,那么接下来执行varnextStateForKey=reducer(previousStateForKey,action)实际上是依次为每个子state片段进行初始化。
[153]本行判定storetree是否被更新,其中nextStateForKey!==previousStateForKey直接通过引用关系判断state是否变更。故一定要注意,定义reducer方法时,一定要遵循函数式编程,确保传入的state与返回的state不要存在引用关系,否则可能导致storetree中状态无法更新。
小结

至此,我们可以看到combineReducers方法,实际就是在每次要执行reducer时,通过action.type定义的类型进行查询,获得子reducer并执行。
通过以上分析,我们需要注意两个问题:
1、子reducer遵循函数式编程,不要直接变更作为参数传入的state,变更state后,一定要返回一个新state对象,不要跟参数state建立引用关系(可以使用Immutable处理state)
2、由于combineReducers内部仅通过action.type作为查询当前要执行的子reducer的依据,故确保所有的子reducer中,action.type的值不要重复,否则仅会执行第一次查询到的reducer
总结

本篇通过分析源码整理了Redux中Store对象的执行逻辑,重点分析了dispatch(action)后,storetree内部状态如何更新。
篇幅所限,没有分析如何在store上注册中间件,以及如何在storetree变更后,触发页面更新的过程,这些会在之后的博客中更新
献花(0)
+1
(本文系thedust79首藏)