一、redux-devtools
我们之前讲过,redux可以方便的让我们对状态进行跟踪和调试,那么如何做到呢?
- redux官网为我们提供了redux-devtools的工具;
- 利用这个工具,我们可以知道每次状态是如何被修改的,修改前后的状态变化等等;
安装该工具需要两步:
- 第一步:在对应的浏览器中安装相关的插件(比如Chrome浏览器扩展商店中搜索Redux DevTools即可,其他方法可以参考GitHub);
- 第二步:在redux中继承devtools的中间件;
index.js:
import {createStore, applyMiddleware, compose} from 'redux'
import reducer from "./reducer.js";
import thunkMiddleware from 'redux-thunk'
// composeEnhancers函数
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) || compose;
// 通过applyMiddleware来结合多个Middleware,返回一个storeEnhancer
const storeEnhancer = applyMiddleware(thunkMiddleware)
const store = createStore(reducer, composeEnhancers(storeEnhancer))
export default store;
二、generator
saga中间件使用了ES6的generator语法,所以我们有必须简单讲解一下:
注意:我这里并没有列出generator的所有用法,事实上它的用法非常的灵活,大家可以自行去学习一下。
我们按照如下步骤演示一下生成器的使用过程:
在JavaScript中编写一个普通的函数,进行调用会立即拿到这个函数的返回结果。
如果我们将这个函数编写成一个生成器函数。调用iterator的next函数,会销毁一次迭代器,并且返回一个yield的结果。
研究一下foo生成器函数代码的执行顺序
generator和promise一起使用:
// generator 和 Promise一起使用
function* bar() {
console.log(111)
const result = yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello Generator')
}, 3000)
})
console.log(result)
}
const it = bar()
// console.log(it.next().value); // Promise对象
it.next().value.then((res) => {
it.next(res)
})
三、redux-saga的使用
redux-saga是另一个比较常用在redux发送异步请求的中间件,它的使用更加的灵活。
Redux-saga的使用步骤如下
- 安装redux-saga
yarn add redux-saga - 集成redux-saga中间件
导入创建中间件的函数;
通过创建中间件的函数,创建中间件,并且放到applyMiddleware函数中;
启动中间件的监听过程,并且传入要监听的saga;
store/index.js:
import {createStore, applyMiddleware, compose} from 'redux'
import reducer from "./reducer.js";
import thunkMiddleware from 'redux-thunk'
import createSagaMiddleware from 'redux-saga'
import saga from './saga'
// composeEnhancers函数
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) || compose;
// 通过applyMiddleware来结合多个Middleware,返回一个storeEnhancer
// 1.引入thunkMiddleware中间件(代码在上面)
// 2.创建sagaMiddleware中间件
const sagaMiddleware = createSagaMiddleware()
const storeEnhancer = applyMiddleware(thunkMiddleware, sagaMiddleware)
const store = createStore(reducer, composeEnhancers(storeEnhancer))
sagaMiddleware.run(saga) // saga为生成器函数
export default store;
3. saga.js文件的编写
takeEvery:可以传入多个监听的actionType,每一个都可以被执行(对应有一个takeLatest,会取消前面的)
put:在saga中派发action不再是通过dispatch,而是通过put;
all:可以在yield的时候put多个action;
store/saga.js:
import {takeEvery, put, all, takeLatest} from 'redux-saga/effects'
import axios from "axios";
import {FETCH_HOME_MULTIDATA} from "./constants";
import {changeBannersAction,changeRecommendsAction} from "./actionCreator";
function* fetchHomeMultidata(action) {
const res = yield axios.get('http://123.207.32.32:8000/home/multidata')
const banners = res.data.data.banner.list
const recommends = res.data.data.recommend.list
// yield put(changeBannersAction(banners))
// yield put(changeRecommendsAction(recommends))
yield all([
yield put(changeBannersAction(banners)),
yield put(changeRecommendsAction(recommends))
])
}
function* mySaga() {
// takeEvery: 每个action都会被执行
// yield takeEvery(FETCH_HOME_MULTIDATA, fetchHomeMultidata)
// takeLatest:一次只能监听一个对应的action
// yield takeLatest(FETCH_HOME_MULTIDATA, fetchHomeMultidata)
yield all([
yield takeEvery(FETCH_HOME_MULTIDATA, fetchHomeMultidata)
// yield takeLatest(FETCH_HOME_MULTIDATA, fetchHomeMultidata)
])
}
export default mySaga
home.js:
import React, {PureComponent} from 'react';
import {connect} from "react-redux";
import {
addAction,
fetchHomeMultidataAction
}from "../store/actionCreator";
class Home extends PureComponent {
componentDidMount() {
this.props.getHomeMultidata()
}
render() {
return (
<div>
<h1>Home</h1>
<h3>当前计数:{this.props.counter}</h3>
<button onClick={e => this.props.increment()}>+1</button>
<button onClick={e => this.props.addNumber(5)}>+5</button>
</div>
);
}
}
const mapStateToProps = state => ({
counter: state.counter
})
const mapDispatchToProps = dispatch => ({
increment() {
dispatch(addAction(1));
},
addNumber(num) {
dispatch(addAction(num));
},
getHomeMultidata() {
dispatch(fetchHomeMultidataAction)
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Home);
四、打印日志需求
前面我们已经提过,中间件的目的是在redux中插入一些自己的操作:
- 比如我们现在有一个需求,在dispatch之前,打印一下本次的action对象,dispatch完成之后可以打印一下最新的store
state; - 也就是我们需要将对应的代码插入到redux的某部分,让之后所有的dispatch都可以包含这样的操作;
如果没有中间件,我们是否可以实现类似的代码呢? 可以在派发的前后进行相关的打印。
但是这种方式缺陷非常明显:
- 首先,每一次的dispatch操作,我们都需要在前面加上这样的逻辑代码;
- 其次,存在大量重复的代码,会非常麻烦和臃肿;
是否有一种更优雅的方式来处理这样的相同逻辑呢?
- 我们可以将代码封装到一个独立的函数中
但是这样的代码有一个非常大的缺陷:
- 调用者(使用者)在使用我的dispatch时,必须使用我另外封装的一个函数dispatchAndLog;
- 显然,对于调用者来说,很难记住这样的API,更加习惯的方式是直接调用dispatch;
修改dispatch
事实上,我们可以利用一个hack一点的技术:Monkey Patching,利用它可以修改原有的程序逻辑;
我们对代码进行如下的修改:
- 这样就意味着我们已经直接修改了dispatch的调用过程;
- 在调用dispatch的过程中,真正调用的函数其实是dispatchAndLog;
当然,我们可以将它封装到一个模块中,只要调用这个模块中的函数,就可以对store进行这样的处理:
thunk需求
redux-thunk的作用:
我们知道redux中利用一个中间件redux-thunk可以让我们的dispatch不再只是处理对象,并且可以处理函数;那么redux-thunk中的基本实现过程是怎么样的呢?事实上非常的简单。
我们来看下面的代码:
我们又对dispatch进行转换,这个dispatch会判断传入的
合并中间件
单个调用某个函数来合并中间件并不是特别的方便,我们可以封装一个函数来实现所有的中间件合并:
我们来理解一下上面操作之后,代码的流程:
当然,真实的中间件实现起来会更加的灵活,这里我们仅仅做一个抛砖引玉,有兴趣可以参考redux合并中间件的源码流程。
五、Reducer代码拆分
我们先来理解一下,为什么这个函数叫reducer?
我们来看一下目前我们的reducer:
- 当前这个reducer既有处理counter的代码,又有处理home页面的数据;
- 后续counter相关的状态或home相关的状态会进一步变得更加复杂;
- 我们也会继续添加其他的相关状态,比如购物车、分类、歌单等等;
- 如果将所有的状态都放到一个reducer中进行管理,随着项目的日趋庞大,必然会造成代码臃肿、难以维护。
因此,我们可以对reducer进行拆分:
- 我们先抽取一个对counter处理的reducer;
- 再抽取一个对home处理的reducer;
- 将它们合并起来;
六、Reducer文件拆分
目前我们已经将不同的状态处理拆分到不同的reducer中,我们来思考:
- 虽然已经放到不同的函数了,但是这些函数的处理依然是在同一个文件中,代码非常的混乱;
- 另外关于reducer中用到的constant、action等我们也依然是在同一个文件中;
七、combineReducers函数
目前我们合并的方式是通过每次调用reducer函数自己来返回一个新的对象。
事实上,redux给我们提供了一个combineReducers函数可以方便的让我们对多个reducer进行合并:
那么combineReducers是如何实现的呢?
事实上,它也是讲我们传入的reducers合并到一个对象中,最终返回一个combination的函数(相当于我们之前的reducer函数了);
在执行combination函数的过程中,它会通过判断前后返回的数据是否相同来决定返回之前的state还是新的state; 新的state会触发订阅者发生对应的刷新,而旧的state可以有效的组织订阅者发生刷新;