redux-saga及中间件的原理~~~~~~~

一.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);

在这里插入图片描述

理解一下上面操作之后,代码的流程:
在这里插入图片描述

当然,真实的中间件实现起来会更加的灵活,这里我们仅仅做一个简单的理解。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值