一.redux-saga
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}
// 打印333333
结果如下:
generator和promise一起使用:
function *bar() {
const result = yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Hello Generator");
return "Hello";
}, 2000);
});
console.log(result);
}
const bIterator = bar();
console.log(bIterator.next().value);//Promise
bIterator.next().value.then(res => {
//yield的第一次执行结果res传给下一次的next
bIterator.next(res);
});
运行结果:
2. redux-saga的使用
2.1.安装redux-saga
yarn add redux-saga
2.2.集成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();
// 通过applyMiddleware来结合多个Middleware, 返回一个enhancer
const enhancer = applyMiddleware(thunkMiddleware, sagaMiddleware);
// 将enhancer作为第二个参数传入到createStore中
const store = createStore(reducer, enhancer);
// 必须启动saga中间件,并且传入其要监听的generator
sagaMiddleware.run(mySaga);
export default store;
2.3.saga.js文件的编写:
takeEvery:可以传入多个监听的actionType,每一个都可以被执行
takeLastest:依次只能监听最后的一个对应的action)
put:在saga中派发action不再是通过dispatch,而是通过put;
all:可以在yield的时候put多个action;
import { takeEvery, put, all, takeLatest } from 'redux-saga/effects';
import axios from 'axios';
import {
FETCH_HOME_MULTIDATA, ADD_NUMBER
} from './constants';
import {
changeBannersAction,
changeRecommendAction
} from './actionCreators';
function* fetchHomeMultidata(action) {
const res = yield axios.get("http://123.207.32.32:8000/home/multidata");
console.log(res);
const banners = res.data.data.banner.list;
const recommends = res.data.data.recommend.list;
// yield put(changeBannersAction(banners));
// yield put(changeRecommendAction(recommends));
yield all([
yield put(changeBannersAction(banners)),
yield put(changeRecommendAction(recommends))
])
}
function* mySaga() {
// takeLatest takeEvery区别:
// takeLatest: 依次只能监听最后一个对应的action
// takeEvery: 每一个都会被执行
yield takeEvery(FETCH_HOME_MULTIDATA,fetchHomeMultidata);
//监听多个action时
// yield all([
// // takeLatest(FETCH_HOME_MULTIDATA, fetchHomeMultidata),
// // takeLatest(ADD_NUMBER, fetchHomeMultidata),
// ]);
}
export default mySaga;
三. 中间件的原理
1. 打印日志需求
前面已经提过,中间件的目的是在redux中插入一些自己的操作:
- 比如现在有一个需求,在dispatch之前,打印一下本次的action对象,dispatch完成之后可以打印一下最新的store state;
- 也就是我们需要将对应的代码插入到redux的某部分,让之后所有的dispatch都可以包含这样的操作;
如果没有中间件,我们是否可以实现类似的代码呢?
当然可以,类似下面的方式即可:
console.log("dispatch前---dispatching action:", addAction(10));
store.dispatch(addAction(10));
console.log("dispatch后---new state:", store.getState());
console.log("dispatch前---dispatching action:", addAction(15));
store.dispatch(addAction(15));
console.log("dispatch后---new state:", store.getState());
但是这种方式缺陷非常明显:
- 首先,每一次的dispatch操作,我们都需要在前面加上这样的逻辑代码;
- 其次,存在大量重复的代码,会非常麻烦和臃肿;
是否有一种更优雅的方式来处理这样的相同逻辑呢?
我们可以将代码封装到一个独立的函数中
function dispatchAndLog(action) {
console.log("dispatching:", action);
store.dispatch(action);
console.log("新的state:", store.getState());
}
dispatchAndLog(addAction(10));
但是这样的代码有一个非常大的缺陷:
- 调用者(使用者)在使用我的dispatch时,必须使用我另外封装的一个函数dispatchAndLog;
- 显然,对于调用者来说,很难记住这样的API,更加习惯的方式是直接调用dispatch;
我们来进一步对代码进行优化;
2. 修改dispatch
事实上,我们可以利用一个hack一点的技术:Monkey Patching,利用它可以修改原有的程序逻辑;
我们对代码进行如下的修改:
- 这样就意味着我们已经直接修改了dispatch的调用过程;
- 在调用dispatch的过程中,真正调用的函数其实是dispatchAndLog;
let next = store.dispatch;
function dispatchAndLog(action) {
console.log("dispatching:", action);
next(action);
console.log("新的state:", store.getState());
}
store.dispatch = dispatchAndLog;
store.dispatch(addAction(5));
当然,我们可以将它封装到一个模块中,只要调用这个模块中的函数,就可以对store进行这样的处理:
function patchLogging(store) {
let next = store.dispatch;
function dispatchAndLog(action) {
console.log("dispatching:", action);
next(action);
console.log("新的state:", store.getState());
}
store.dispatch = dispatchAndLog;
}
3. thunk需求
redux-thunk的作用:
- 我们知道redux中利用一个中间件redux-thunk可以让我们的dispatch不再只是处理对象,并且可以处理函数;
- 那么redux-thunk中的基本实现过程是怎么样的呢?事实上非常的简单。
我们来看下面的代码:
- 我们又对dispatch进行转换,这个dispatch会判断传入的
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;
}
将两个patch应用起来,进行测试:
patchLogging(store);
patchThunk(store);
store.dispatch(addAction(10));
function getData(dispatch) {
setTimeout(() => {
dispatch(subAction(10));
}, 1000)
}
// 传入函数
store.dispatch(getData);
4. 合并中间件
单个调用某个函数来合并中间件并不是特别的方便,我们可以封装一个函数来实现所有的中间件合并:
function applyMiddlewares(...middlewares) {
// const newMiddleware = [...middlewares];
console.log(middlewares);
middlewares.forEach(middle => {
console.log(middle);
store.dispatch = middle(store);
})
}
applyMiddlewares(patchLogging, patchThunk);
理解一下上面操作之后,代码的流程:
当然,真实的中间件实现起来会更加的灵活,这里我们仅仅做一个简单的理解。