Redux学习——redux-saga的使用编写中间件函数Reducer文件拆分
Posted 小小白学计算机
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redux学习——redux-saga的使用编写中间件函数Reducer文件拆分相关的知识,希望对你有一定的参考价值。
一、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可以有效的组织订阅者发生刷新;
八、React中的state如何管理
以上是关于Redux学习——redux-saga的使用编写中间件函数Reducer文件拆分的主要内容,如果未能解决你的问题,请参考以下文章
为啥使用 Redux-Observable 而不是 Redux-Saga?