状态管理
无计划的状态管理
redux 本身是一个状态管理器,可以对数据状态进行一系列的操作,例如
|
|
以上即为最简单的状态保存和修改,但是这里有一个问题,即不能在 count 更改后通知使用 count 的地方,这里我们可以用订阅/发布者模式实现一下:
|
|
这里我们实现了一个简单的状态管理器,主要导出 subscribe(订阅)/changeState(修改)/getState(获取)三个接口。下面可以使用例子测试一下:
|
|
代码见 demo1
有计划的状态管理
上面的代码存在一些问题,例如:
|
|
我们会发现 counter 的结构直接被改变了,状态的修改没有任何约束。
这里我们只想要 count 有自增或者自减两种状态改变,那么我们可以只暴露自增和自减两种方法,整个实现分为两步:
- 制定一个 plan 来修改 count 的状态,里面包含自增和自减两个方法。
- 修改 changeState 方法,在执行时按照 plan 的方法执行。
|
|
现在来测试一下
|
|
现在我们规定了数据的改变方式,只在我们允许的 action.type 中改变数据状态。在 redux 中,reducer 就相当于现在的 plan,dispatch 相当于现在的 changeState。
代码见 demo2
拆分 reducer
在一个项目中,势必会有许多不同组件 state 的 reducer,像之前的写法势必会让一个 reducer 文件过于庞大而且难以维护,那么这里可以将不同组件 state 的 reducer 拆分成单个文件,最后再组合起来。例如对于以下状态
|
|
我们可以拆分为 counterReducer 和 infoReducer
|
|
接下来就是创建一个方法将两个 reducer 合并起来,我们想要的是如下的执行方法
|
|
那么我们需要根据传入对象的 key-value 来返回一个 function,这个 function 需要对所有的 reducer 执行并返回结合后的状态(返回了整个状态树)
|
|
接下来可以看下实际的使用例子:
|
|
代码见 demo3
拆分不同组件的 state
上面我们拆分了 reducer,同样的,实际业务中将整个 store 定义在一起也会出现单个对象结构过于复杂的问题,那么这里我们将 state 拆分到各自的 reducer 中。如下:
|
|
相应的,在执行 createStore 函数时,需要将所有状态的初始值提取出来组合 state,考虑到所有的 reducer 在switch
判断进入default
时会返回 state 自身,可以通过 dispatch 一次唯一的 type 的类型来获取初始值,这里使用 es6 中的 Symbal 来当唯一的 type,保证所有 reducer 进入default
。
|
|
来执行一下
|
|
代码见 demo4
中间件 middleware
中间件在 redux 中是一个比较难以理解的概念,中间件是通过对 redux 中 dispatch 重写,来实现增强其功能的目的。
记录日志
简单来说,我们现在需要增加一个日志功能,需要记录每次修改操作前的数据和修改后的数据,因为之前的数据 subscribe 无法记录,这里我们就需要一个简单的中间件来实现了
|
|
记录异常
这是我们又有另一个需求需要记录 redux 修改状态时的异常。那么使用中间件:
|
|
这样每次修改出现异常的话就会打印出来。
中间件的合作
那么加入我们要同时实现上面两个需求呢?最简单的是直接合并起来写进同一个函数体内,但是如果需求变多的话,一个store.dispatch
会变得非常复杂。这里我们想办法将不同功能独立出去。
- 首先我们先把 loggerMiddleware 提取出来:
|
|
- 把 exceptMiddleware 提取出来:
|
|
- 这里有一个问题,exceptMiddleware 中写死了 loggerMiddleware 中间件,我们需要可以动态传入,随便哪个中间件都可以:
|
|
- 同样的,loggerMiddleware 中的 next 也恒等于
store.dispatch
,照着上面也写成动态的。
|
|
至此,我们已经实现了一个扩展性很强的中间件模式,那么完整的写法就是
|
|
当我们新建了两个文件loggerMiddleware.js
和exceptMiddleware.js
两个文件,想要把两个方法分离到两个文件中时,新的问题出现了,我们的 store 是唯一的,两个方法都需要 store,那么我们将 store 也分离出去
|
|
至此我们成功实现了中间件,假如我们新加入一个在打印日志前输出当前时间戳的需求呢
|
|
代码见 demo5
优化中间件使用方法
在上面使用中间件时,需要用 store 作为参数对每个中间件显式传参,最后还要写成嵌套的函数,用起来太过繁琐、不直观。我们期望的应该是直接将中间件按顺序传入某个函数后自动生成,因为最终修改的是dispatch
,那我们直接修改 createStore 函数来实现。下面是期望的使用形式。
|
|
具体实现
|
|
写到这里,我们有两个 createStore,例如:
|
|
我们可以统一写进 createStore 函数中,通过选填的参数进行判断生成 store
|
|
最终用法
|
|
代码见 demo6
完善 redux
- 添加事件解绑,修改
store.subscribe
方法。
|
|
使用
|
|
2.控制中间件权限,现在的中间件拿到的是完整的 store,中间件甚至可以修改 store 上的方法,按照最小权限的原则,我们只需要传store.getState
方法即可。
|
|
- 省略initState
有时候创建store时我们不传入initState,此时我们的写法为1const store = createStore(reducer, {}, rewriteCreateStoreFunc);
redux允许这么写
这里只需要判断下第二个参数是否为对象再进行下一步操作
- bindActionCreators
这个功能一般出现在react-redux中,通过闭包隐藏dispatch和actionCreator,只暴露action来进行状态操作。
这里我们先自己实现一下隐藏dispatch和actionCreator1234567891011121314151617181920212223242526272829303132const reducer = combineReducers({counter: counterReducer,info: infoReducer});const store = createStore(reducer);/*返回 action 的函数就叫 actionCreator*/function increment() {return {type: 'INCREMENT'}}function setName(name) {return {type: 'SET_NAME',name: name}}const actions = {increment: function () {return store.dispatch(increment.apply(this, arguments))},setName: function () {return store.dispatch(setName.apply(this, arguments))}}/*注意:我们可以把 actions 传到任何地方去*//*其他地方在实现自增的时候,根本不知道 dispatch,actionCreator等细节*/actions.increment(); /*自增*/actions.setName('九部威武'); /*修改 info.name*/
那么我们现在可以提炼一下整个函数的使用形式
以下是bindActionCreators的源码实现
总结
总结一下整个过程中使用的redux中的名词
- createStore
返回store对象,包含dispatch,getState,subscribe等方法 - reducer
计划函数,接收action和newState,表示可以怎样对状态进行修改 - action
action是一个包含type字段的对象。 - dispatch
接收action,通知store进行状态修改。 - subscribe
每次状态改变执行参数中的回调。 - combineReducers
合并计划函数。 - middleWare
中间件,对原dispatch的功能增强。
附:
- redux功能图
- 代码地址