房地产网站制作教程,网站建设 汇卓,中小型企业网站的设计与开发,渭南哪家公司可以做网站本文来自#React系列教程#xff1a;https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNAactiongetalbumalbum_id1566025152667107329) 一. 中间件的使用
1.1. 组件中异步请求
在之前简单的案例中#xff0c;redux中保存的counter是一个本地定义的数据… 本文来自#React系列教程https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNAactiongetalbumalbum_id1566025152667107329) 一. 中间件的使用
1.1. 组件中异步请求
在之前简单的案例中redux中保存的counter是一个本地定义的数据我们可以直接通过同步的操作来dispatch actionstate就会被立即更新。
但是真实开发中redux中保存的很多数据可能来自服务器我们需要进行异步的请求再将数据保存到redux中。
在之前学习网络请求的时候我们讲过网络请求可以在class组件的componentDidMount中发送所以我们可以有这样的结构 我现在完成如下案例操作
在Home组件中请求banners和recommends的数据在Profile组件中展示banners和recommends的数据
redux代码进行如下修改
在reducer.js中添加state初始化数据和reducer函数中处理代码
const initialState {counter: 0,banners: [],recommends: []
}function reducer(state initialState, action) {switch (action.type) {case ADD_NUMBER:return { ...state, counter: state.counter action.num };case SUB_NUMBER:return { ...state, counter: state.counter - action.num };case CHANGE_BANNER:return { ...state, banners: action.banners };case CHANGE_RECOMMEND:return { ...state, recommends: action.recommends };default:return state;}
}constants中增加常量
const CHANGE_BANNER CHANGE_BANNER;
const CHANGE_RECOMMEND CHANGE_RECOMMEND;actionCreators.js中添加actions
const changeBannersAction (banners) ({type: CHANGE_BANNER,banners
}) const changeRecommendsAction (recommends) ({type: CHANGE_RECOMMEND,recommends
})组件中代码代码修改
Home组件
import React, { PureComponent } from react;
import { connect } from react-redux;import axios from axios;import {addAction,changeBannersAction,changeRecommendsAction
} from ../store/actionCreators;class Home extends PureComponent {componentDidMount() {axios.get(http://123.207.32.32:8000/home/multidata).then(res {const data res.data.data;this.props.changeBanners(data.banner.list);this.props.changeRecommends(data.recommend.list);})}...其他业务代码
}const mapStateToProps state {return {counter: state.counter}
}const mapDispatchToProps dispatch {return {addNumber: function(number) {dispatch(addAction(number));},changeBanners(banners) {dispatch(changeBannersAction(banners));},changeRecommends(recommends) {dispatch(changeRecommendsAction(recommends));}}
}export default connect(mapStateToProps, mapDispatchToProps)(Home);Profile组件
import React, { PureComponent } from react;
import { connect } from react-redux;import {subAction
} from ../store/actionCreators;class Profile extends PureComponent {render() {return (divProfiledivh2当前计数: {this.props.counter}/h2button onClick{e this.decrement()}-1/buttonbutton onClick{e this.subCounter()}-5/button/divh1Banners/h1ul{this.props.banners.map((item, index) {return li key{item.acm}{item.title}/li})}/ulh1Recommends/h1ul{this.props.recommends.map((item, index) {return li key{item.acm}{item.title}/li})}/ul/div)}...其他逻辑代码
}const mapStateToProps state {return {counter: state.counter,banners: state.banners,recommends: state.recommends}
}const mapDispatchToProps dispatch {return {subNumber: function (number) {dispatch(subAction(number));}}
}export default connect(mapStateToProps, mapDispatchToProps)(Profile);1.2. redux中异步请求
上面的代码有一个缺陷
我们必须将网络请求的异步代码放到组件的生命周期中来完成事实上网络请求到的数据也属于我们状态管理的一部分更好的一种方式应该是将其也交给redux来管理 但是在redux中如何可以进行异步的操作呢
答案就是使用中间件Middleware学习过Express或Koa框架的童鞋对中间件的概念一定不陌生在这类框架中Middleware可以帮助我们在请求和响应之间嵌入一些操作的代码比如cookie解析、日志记录、文件压缩等操作
redux也引入了中间件Middleware的概念
这个中间件的目的是在dispatch的action和最终达到的reducer之间扩展一些自己的代码比如日志记录、调用异步接口、添加代码调试功能等等
我们现在要做的事情就是发送异步的网络请求所以我们可以添加对应的中间件
这里官网推荐的、包括演示的网络请求的中间件是使用 redux-thunk
redux-thunk是如何做到让我们可以发送异步的请求呢
我们知道默认情况下的dispatch(action)action需要是一个JavaScript的对象redux-thunk可以让dispatch(action函数)action可以是一个函数该函数会被调用并且会传给这个函数一个dispatch函数和getState函数 dispatch函数用于我们之后再次派发actiongetState函数考虑到我们之后的一些操作需要依赖原来的状态用于让我们可以获取之前的一些状态
如何使用 redux-thunk 呢
安装 redux-thunk
yarn add redux-thunk在创建store时传入应用了middleware的enhance函数
通过applyMiddleware来结合多个Middleware, 返回一个enhancer将enhancer作为第二个参数传入到createStore中
// 通过applyMiddleware来结合多个Middleware, 返回一个enhancer
const enhancer applyMiddleware(thunkMiddleware);
// 将enhancer作为第二个参数传入到createStore中
const store createStore(reducer, enhancer);定义返回一个函数的action
const getHomeMultidataAction () {return (dispatch) {axios.get(http://123.207.32.32:8000/home/multidata).then(res {const data res.data.data;dispatch(changeBannersAction(data.banner.list));dispatch(changeRecommendsAction(data.recommend.list));})}
}注意这里不是返回一个对象了而是一个函数该函数在dispatch之后会被执行
修改home.js中的代码
import React, { PureComponent } from react;
import { connect } from react-redux;import {addAction,getHomeMultidataAction
} from ../store/actionCreators;class Home extends PureComponent {componentDidMount() {this.props.getHomeMultidata();}...其他逻辑代码
}...mapStatetoPropsconst mapDispatchToProps dispatch {return {addNumber: function(number) {dispatch(addAction(number));},getHomeMultidata() {dispatch(getHomeMultidataAction());}}
}export default connect(mapStateToProps, mapDispatchToProps)(Home);1.3. redux-devtools
我们之前讲过redux可以方便的让我们对状态进行跟踪和调试那么如何做到呢
redux官网为我们提供了redux-devtools的工具利用这个工具我们可以知道每次状态是如何被修改的修改前后的状态变化等等
安装该工具需要两步
第一步在对应的浏览器中安装相关的插件比如Chrome浏览器扩展商店中搜索Redux DevTools即可其他方法可以参考GitHub第二步在redux中集成devtools
import { createStore, applyMiddleware, compose } from redux;
import thunkMiddleware from redux-thunk;
import reducer from ./reducer.js;const composeEnhancers window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;// 通过applyMiddleware来结合多个Middleware, 返回一个enhancer
const enhancer composeEnhancers(applyMiddleware(thunkMiddleware));
// 将enhancer作为第二个参数传入到createStore中
const store createStore(reducer, enhancer);export default store;trace打开
const composeEnhancers window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) || compose;1.4. redux-saga
1.4.1. ES6的generator
saga中间件使用了ES6的generator语法所以我们有必须简单讲解一下
注意我这里并没有列出generator的所有用法事实上它的用法非常的灵活大家可以自行去学习一下。
在JavaScript中编写一个普通的函数进行调用会立即拿到这个函数的返回结果
function foo() {return Hello World;
}foo() // Hello World如果我们将这个函数编写成一个生成器函数
function *foo() {yield Hello;yield World;
}const iterator foo();
console.log(iterator, typeof iterator); // 一个object类型的iterator对象调用iterator的next函数会销毁一次迭代器并且返回一个yield的结果
// 调用一次next()是消耗一次迭代器
iterator.next(); // {value: Hello, done: false}
iterator.next(); // {value: World, done: false}
iterator.next(); // {value: undefined, done: true}研究一下foo生成器函数代码的执行顺序
function *foo() {console.log(111111);yield Hello;console.log(222222);yield World;console.log(333333);
}// 调用一次next()是消耗一次迭代器
iterator.next(); // {value: Hello, done: false}
// 打印111111
iterator.next(); // {value: World, done: false}
// 打印222222
iterator.next(); // {value: undefined, done: true}
// 打印333333generator和promise一起使用
function *bar() {const result yield new Promise((resolve, reject) {setTimeout(() {resolve(Hello Generator);return Hello;}, 2000);});console.log(result);
}const bIterator bar();
bIterator.next().value.then(res {bIterator.next(res);
});1.4.2. redux-saga的使用
装 redux-saga
yarn add redux-saga集成 redux-saga 中间件
import { createStore, applyMiddleware, compose } from redux;
import thunkMiddleware from redux-thunk;
import createSagaMiddleware from redux-saga;
import reducer from ./reducer.js;
import mySaga from ./saga;// 通过createSagaMiddleware函数来创建saga中间件
const sagaMiddleware createSagaMiddleware();const composeEnhancers window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) || compose;// 通过applyMiddleware来结合多个Middleware, 返回一个enhancer
const enhancer composeEnhancers(applyMiddleware(thunkMiddleware, sagaMiddleware));
// 将enhancer作为第二个参数传入到createStore中
const store createStore(reducer, enhancer);// 必须启动saga中间件并且传入其要监听的generator
sagaMiddleware.run(mySaga);export default store;saga.js 文件的编写
import { takeEvery, put, all } from redux-saga/effects;
import axios from axios;import {FETCH_HOME_MULTIDATA
} from ./constants;
import {changeBannersAction,changeRecommendsAction,
} from ./actionCreators;function* fetchHomeMultidata(action) {const res yield axios.get(http://123.207.32.32:8000/home/multidata);console.log(res);const data res.data.data;yield all([put(changeBannersAction(data.banner.list)),put(changeRecommendsAction(data.recommend.list))])
}function* mySaga() {yield takeEvery(FETCH_HOME_MULTIDATA, fetchHomeMultidata)
}export default mySaga;takeEvery可以传入多个监听的actionType每一个都可以被执行对应有一个takeLastest会取消前面的put在saga中派发action不再是通过dispatch而是通过putall可以在yield的时候put多个action
二. 中间件的原理
2.1. 打印日志需求
前面我们已经提过中间件的目的是在redux中插入一些自己的操作
比如我们现在有一个需求在dispatch之前打印一下本次的action对象dispatch完成之后可以打印一下最新的store state也就是我们需要将对应的代码插入到redux的某部分让之后所有的dispatch都可以包含这样的操作
如果没有中间件我们是否可以实现类似的代码呢
当然可以类似下面的方式即可
console.log(dispatching:, addAction(5));
store.dispatch(addAction(5));
console.log(new state:, store.getState());console.log(dispatching:, addAction(10));
store.dispatch(subAction(10));
console.log(new state:, store.getState());但是这种方式缺陷非常明显
首先每一次的dispatch操作我们都需要在前面加上这样的逻辑代码其次存在大量重复的代码会非常麻烦和臃肿
是否有一种更优雅的方式来处理这样的相同逻辑呢
我们可以将代码封装到一个独立的函数中
function dispatchAndLog(action) {console.log(dispatching:, action);store.dispatch(addAction(5));console.log(新的state:, store.getState());
}dispatchAndLog(addAction(10));但是这样的代码有一个非常大的缺陷
调用者使用者在使用我的dispatch时必须使用我另外封装的一个函数dispatchAndLog显然对于调用者来说很难记住这样的API更加习惯的方式是直接调用dispatch
我们来进一步对代码进行优化
2.2. 修改dispatch
事实上我们可以利用一个hack一点的技术Monkey Patching利用它可以修改原有的程序逻辑
我们对代码进行如下的修改
let next store.dispatch;function dispatchAndLog(action) {console.log(dispatching:, addAction(10));next(addAction(5));console.log(新的state:, store.getState());
}store.dispatch dispatchAndLog;这样就意味着我们已经直接修改了dispatch的调用过程在调用dispatch的过程中真正调用的函数其实是dispatchAndLog 当然我们可以将它封装到一个模块中只要调用这个模块中的函数就可以对store进行这样的处理
function patchLogging(store) {let next store.dispatch;function dispatchAndLog(action) {console.log(dispatching:, action);next(addAction(5));console.log(新的state:, store.getState());}store.dispatch dispatchAndLog;
}2.3. thunk需求
redux-thunk的作用
我们知道redux中利用一个中间件redux-thunk可以让我们的dispatch不再只是处理对象并且可以处理函数那么redux-thunk中的基本实现过程是怎么样的呢事实上非常的简单。
我们来看下面的代码
function patchThunk(store) {let next store.dispatch;function dispatchAndThunk(action) {if (typeof action function) {action(store.dispatch, store.getState);} else {next(action);}}store.dispatch dispatchAndThunk;
}我们又对dispatch进行转换这个dispatch会判断传入的
将两个patch应用起来进行测试
patchLogging(store);
patchThunk(store);store.dispatch(addAction(10));function getData(dispatch) {setTimeout(() {dispatch(subAction(10));}, 1000)
}// 传入函数
store.dispatch(getData);2.4. 合并中间件
单个调用某个函数来合并中间件并不是特别的方便我们可以封装一个函数来实现所有的中间件合并
function applyMiddleware(store, middlewares) {middlewares middlewares.slice();middlewares.forEach(middleware {store.dispatch middleware(store);})
}applyMiddleware(store, [patchLogging, patchThunk]);我们来理解一下上面操作之后代码的流程 当然真实的中间件实现起来会更加的灵活这里我们仅仅做一个抛砖引玉有兴趣可以参考redux合并中间件的源码流程。