您的位置:首页 > 教程 > JavaScript > React开发进阶redux saga使用原理详解

React开发进阶redux saga使用原理详解

2022-11-28 15:08:35 来源:易采站长站 作者:

React开发进阶redux saga使用原理详解

目录前言redux的特点分析原理1.自动执行Generator2.发布订阅模式3.put,takeEvery,delay,call返回effect总结前言工作中使用了redux-saga这...

目录
前言
redux的特点
分析原理
1. 自动执行Generator
2. 发布订阅模式
3. put, takeEvery, delay, call返回effect
总结

前言

工作中使用了redux-saga这个redux中间件,如果不明白内部原理使用起来会让人摸不着头脑,阅读源码后特意对其原理做下总结。

redux的特点

一个标准、管理应用副作用的redux中间件
实现切面编程方式
声明式的编写方式

订阅发布的设计模式

优点:

把异步操作转移到单独 saga文件中,而不是糅杂在action或者component中;
dispatch的参数保持为纯粹的action而不是thunk function;
大量的saga辅助函数和effect创建器减少了开发者的开发成本;
灵活的串行或并行能够实现复杂的异步流程。

分析原理

先举个实践的例子

import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import thunk from 'redux-thunk';
import { put, takeEvery, delay, call, select } from 'redux-saga/effects';
const reducer = (state = 0, action) => {
  switch (action.type) {
    case 'put':
      return state + action.payload;
    default:
      return state;
  }
};
const sagaMiddleware = createSagaMiddleware()
export const store = createStore(reducer, applyMiddleware(sagaMiddleware, thunk));
function* main() {
  // 工具函数 delay 阻塞1s
  var start = Date.now();
  yield delay(1000);
  console.log(Date.now() - start);// 1秒多
  // put 类似于 dispatch
  yield put({ type: 'put' , payload:10});
  // takeEvery 不阻塞程序
  yield takeEvery('takeEvery111', function ({ type, payload }) {
    console.log('takeEvery', type, payload); // yield put({ type: 'takeEvery111' , payload:10}); 触发
  });
  // select 获取state中的数据
  const state = yield select((state) => state);
  console.log(state); // 10
  // call 阻塞程序
  yield call(function* () { // 阻塞
    yield delay(1000);
  });
  console.log(Date.now() - start);// 2秒多
  yield put({ type: 'takeEvery111' , payload:10});
}
sagaMiddleware.run(main);

依次打印出如下结果:

1001
10
2004
takeEvery takeEvery111 10

1. 自动执行Generator

从执行结果来看,这个main函数能自动按顺序执行说明在redux-saga的程序代码中有自动执行gen的机制,其实源码就是./internal/proc.js文件中,通过函数之间循环调用的方式执行这个gen函数。

这样的自动执行机制在generator中是比较常见的,比如co模块就具有这样的功能,其实现巧妙却不复杂,如下例子:

function makePromisify(source) {
    if (source.then && typeof source.then === "function") return source
    return Promise.resolve(source)
}
function run(generatorFunc) {
    let it = generatorFunc()
    let result = it.next()
    return new Promise((resolve, reject) => {
        const next = function (result) {
            if (result.done) {
                resolve(result.value)
            }
            //保证返回的是一个promise
            result.value = makePromisify(result.value)
            result.value.then(res => {
                //将promise的返回值res传入iterator迭代器的next方法中,作为yield后面表达式的返回值
                //it.next将停止的yield继续执行到下一个yield,返回的result是一个value,done属性组成的对象
                let result = it.next(res)
                //递归执行next函数
                next(result)
            }).catch(err => {
                reject(err)
            })
        }
        next(result)
    })
}

2. 发布订阅模式

我们看到takeEvery是可以拦截到{type: 'takeEvery111'}这个action,说明在redux-saga内部有类似发布订阅on/trigger这样的机制,通过阅读源码我们发现,内部通过channel这个东西来做发布订阅的处理;在./internal/channel.js就有这样的源码:

put(input) {
  const takers = (currentTakers = nextTakers)
  for (let i = 0, len = takers.length; i < len; i++) {
    const taker = takers[i]
    // 如果take匹配到, 执行它
    if (taker[MATCH](input)) {
      taker.cancel()
      taker(input) // 发布
    }
  }
},
take(cb, matcher = matchers.wildcard) {
  cb[MATCH] = matcher
  ensureCanMutateNextTakers()
  nextTakers.push(cb) // 订阅
},

3. put, takeEvery, delay, call返回effect

put执行并不是直接dispatch一个action,而是通过yield向redux-saga内部传递参数(这个参数在redux-saga中叫effect),内部根据这个参数决定具体的执行内容。

在源码中put、fork、call 等是通过 makeEffect 创建了一系列 effect,这个 effect 是一个普通的 js 对象,上面挂载了一些相关的信息,并且把这个effect yield到内部的runEffejct中,然后根据type字段决定真正需要执行的程序过程。

const makeEffect = (type, payload) => ({
  [IO]: true,
  combinator: false,
  type,
  payload,
});

总结

redux-saga还有许多要探索的地方,比如channel、状态机的应用、任务队列,等。。。更多关于React进阶redux saga原理的资料请关注我们其它相关文章!

如有侵权,请发邮件到 [email protected]

相关文章

  • vue跳转页面的几种方法(推荐)

    vue跳转页面的几种方法(推荐)

    vue跳转不同页面的多种方法 1:router-link跳转 !-- 直接跳转 --router-link to='/testDemo' button点击跳转2/button/router-link !-- 带参数跳转 --router-link :to="{path:'testDemo',query:{setid:123456}}" button点击跳转
    2020-03-26
  • Vue+elementUI实现多图片上传与回显功能(含回显后继续上传或删除

    Vue+elementUI实现多图片上传与回显功能(含回显后继续上传或删除

    最近有使用vue+elementUI实现多图片上传的需求,遂做此纪录。 本次主要写一下前端的实现细节,至于后台以Multipart[ ]数组接收即可,不再赘述,网上一搜大把文章可供参考。 本次使用
    2020-03-23
  • 微信小程序搜索框样式并实现跳转到搜索页面(小程序搜索功能

    微信小程序搜索框样式并实现跳转到搜索页面(小程序搜索功能

    上效果图: 一:搜索框功能实现 1.在首页做一个搜索框的样式并实现跳转到搜索页面 view class='page_row' bindtap="suo" view class="search" view class="df search_arr" icon class="searchcion" size='20' type='search'/
    2020-03-10
  • 微信小程序实现canvas分享朋友圈海报

    微信小程序实现canvas分享朋友圈海报

    本文实例为大家分享了微信小程序分享朋友圈海报的具体代码,供大家参考,具体内容如下 思路:生成朋友圈海报放在公共文件,首先需要绘制canvas,点击分享朋友圈按钮,在手机屏幕
    2020-06-21
  • 详解ES6 Modules

    详解ES6 Modules

    当下, 我们几乎所有的项目都是基于 webpack、rollup 等构建工具进行开发的,模块化已经是常态。 我们对它并不陌生,今天,我们就再系统的回顾一下ES6的模块机制, 并总结下常用的操
    2020-07-04
  • Vue-router 报错NavigationDuplicated的解决方法

    Vue-router 报错NavigationDuplicated的解决方法

    版本:3.1.x 报错原因: 使用push()、replace()进行导航时,不能重复导航到当前路由。 解决办法: 方法1:在定义路由的文件中router/index.js const originalPush = VueRouter.prototype.pushVueRouter.protot
    2020-03-31
  • vue使用better-scroll实现滑动以及左右联动

    vue使用better-scroll实现滑动以及左右联动

    本文实例为大家分享了vue实现滑动以及左右联动效果的具体代码,供大家参考,具体内容如下 一、首先需要在项目中引入better-scroll 1. 在package.json 直接写入 "better-scroll":"^1.15.1" 版本以
    2020-06-30
  • Vue如何提升首屏加载速度实例解析

    Vue如何提升首屏加载速度实例解析

    在Vue项目中,引入到工程中的所有js、css文件,编译时都会被打包进vendor.js,浏览器在加载该文件之后才能开始显示首屏。若是引入的库众多,那么vendor.js文件体积将会相当的大,影响
    2020-06-25