redux-saga入门[通俗易懂]

redux-saga入门[通俗易懂]redux-saga的介绍、API、使用案例

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺


资料

文档:

参考文章:

redux-saga简介

使用 dispatch 往 store 发送 action 的这个过程是可以被拦截的, 自然而然地就可以在这里增加各种中间件Middleware。redux-saga是redux的中间件,主要负责从action派发到更新store中间具有副作用行为的处理。
在这里插入图片描述

sagas.js文件

import { 
    all, put, takeEvery } from 'redux-saga/effects'

function* increment() { 
   
    // 相当于:dispatch({ type: 'increment' })
    yield put({ 
    type: 'increment' }) 
}

function* watchIncrement() { 
   
    // 监听类型为increment_saga的action,监听到启动increment
    yield takeEvery('increment_saga', increment) 
}

function* rootSaga() { 
   
    // 启动watchIncrement
    yield all([watchIncrement()])
}

export default rootSaga

这些generator函数我们叫它saga。saga中yield 后面的内容我们称呼它为Effect(redux-saga的任务单元),在Effects中我们可以进行启动其它saga,也可以处理一些副作用操作。Effects是一些简单对象,如下put({ type: ‘increment’ }),我们使用redux-saga提供的put方法创建一个Effect对象。如果直接打印Effect:

console.log(put({ 
    type: 'increment' }));

在这里插入图片描述

在 redux-saga 的世界里,Saga 都用 Generator 函数实现。我们从 Generator 里 yield 纯 JavaScript 对象以表达 Saga 逻辑。 我们称呼那些对象为 Effect。Effect 是一个简单的对象,这个对象包含了一些给 middleware 解释执行的信息。 你可以把 Effect 看作是发送给 middleware 的指令以执行某些操作(调用某些异步函数,发起一个 action 到 store,等等)。

常用API

takeLatest

takeLatest是非阻塞的。

顾名思义takeEvery监听每一次对应action的派发,而takeLatest监听最后一次action的派发,并自动取消之前已经在启动且任在执行的任务。 这个和我们的防抖很类似。

takeLatest(pattern, saga, …args): 监听类型为pattern的action的派发,当监听到该类型的action,将执行第二个参数saga,且如果存在上一次已经启动且仍在运行的该saga,takeLatest将取消上一次该saga的运行。

  • pattern:takeLatest将监听类型为pattern的action的派发。takeLatest第一个参数是*,即不再匹配某一个具体的action,而是匹配所有的action
  • saga:监听到对应action,启动对应saga。
  • args:传递给saga函数的参数。如果takeLatest没有传入args,那么saga函数的参数只有一个,即类型为pattern的action。如果takeLatest传入了其它args参数,那么saga函数的参数将像这样(args,action)。

takeLatest会创建一个一直执行的任务,该任务的功能是监听类型为xxx的action执行对应的saga。且takeLatest是非阻塞的,即中间件接收到takeLatest创建的Effect之后就去创建一个一直执行的任务,同时继续执行yield takeLatest(‘xxx’, login)后面的代码。takeEvery同理。

function* increment() { 
   
    yield put({ 
    type: 'increment' }) 
}

function* watchIncrement() { 
   
    yield takeLatest('increment_saga', increment) 
}

takeEvery

takeEvery是非阻塞的。

takeEvery(pattern, saga, ...args): 监听类型为pattern的action的派发,当监听到该类型的action,将执行第二个参数saga,且args将作为参数传递给saga函数,与takeLatest唯一不同即 不会取消之前监听到类型pattern的action且正在执行的saga任务。

function* increment() { 
   
    yield put({ 
    type: 'increment' }) 
}

function* watchIncrement() { 
   
    // 监听类型为increment_saga的action,监听到启动increment
    yield takeEvery('increment_saga', increment) 
}

take

take是阻塞的。

take接受的参数type即用来匹配action的类型,take作用是创建一个Effect,命令中间件等待指定的action到来(下方代码是等待类型为loginOut的action到来),只监听一次。在该action到来之前,将暂停当前Generator。take()的返回值是当前aciton。

function* loginOut() { 
   
    yield put({ 
    type: 'loginSuccess', loginInfo: { 
    success: false, name: '', password: '' } })
}
// 监听登录登出saga
function* watchLogin() { 
   
    yield takeLatest('login', login)
  
    // 1,使用take等待类型loginOut的action的到来,take将阻塞当前Generator
    const action = yield take('loginOut')
    // 2,take监听到类型loginOut的action,执行yield call(loginOut),即继续登出操作
    yield call(loginOut)
}

当yield take(‘loginOut’)等待的类型为loginOut的action到来时,Generator开始继续执行后面的代码,即 yield call(loginOut),call将创建一个Effect,命令中间件执行loginOut方法,完成后续的登出操作,因为call方法是阻塞的,所以当前Generator会等待loginOut的完成。当loginOut完成之后,该Generator函数将执行完毕。

Generator函数(即watchLogin)将执行完毕退出后,此时只有yield takeLatest(‘login’, login)创建的监听login的任务仍然在后台运行。所以往后再有退出的loginOut类型的action派发过来的,并没有任何任务对其保持监听,所以将中间件将忽略掉后续过来的loginOut类型的action。

delay

delay(timeout,[val]): 产生一个阻塞的Effect(Effect=>任务单元),阻塞timeout毫秒,并返回val(val非必传)。大白话就是yield delay(1000,‘Love U’)将阻塞当前代码执行1000ms,并且返回’Love U’。

import { 
    put, delay } from 'redux-saga/effects'

function* increment() { 
   
    yield put({ 
    type: 'increment' }) // 相当于:dispatch({ type: 'increment' })
}

function* incrementAsync() { 
   
    // 延迟1s
    yield delay(1000)
    // 1s后,dispatch({ type: 'increment' })
    yield put({ 
    type: 'increment' })
}

put

yield put({ 
    type: 'increment' })

put方法的参数为一个action,其作用是产生一个dispatch行为的Effect,其action就是put中的参数action。我们可以理解这个动作就相当于dispatch({ type: 'increment' })。所以这里将派发一个类型为increment动作去更新store中的state。

call

call是阻塞调用的。

call(fn, args):它将创建一个Effect,用来命令中间件以args参数调用fn,fn可以是一个Generator函数也可以是一个返回Pormise或任意其他值的普通函数。如果返回Promise将获得Promise的value。

call创建的Effect会命令中间件调用传入的函数,并检查其结果,如果结果是迭代器对象或者是Promise实例中间件将一直暂停当前saga直到迭代器对象或Promise实例处理完毕。如果返回结果不是迭代器对象,则中间件会立即把该值交给call所在的saga,从而让saga可以以同步的形式恢复执行。

function loginService({ 
     name, password }) { 
   
    return new Promise((resolve, reject) => { 
   
        setTimeout(() => { 
   
            if (name === 'admin' && password === 'admin') { 
   
                resolve({ 
    success: true, name, password })
            } else { 
   
                reject({ 
    success: false, msg: '账户不合法' })
            }
        }, 1000)
    })
}

function* login(action) { 
   
    try { 
   
        // 调用loginService,获取登录成功失败的信息
        const loginInfo = yield call(loginService, action.account)
        yield put({ 
    type: 'loginSuccess', loginInfo })
    } catch (error) { 
   
        alert(error.msg)
    }
}

fork

fork是非阻塞调用的。

fork(fn, ...args) : fork创建一个Effect,命令中间件以非阻塞的形式调用fn,且返回一个task对象类似非阻塞形式的call。fork表现形式为创建一个分叉的task去执行fn,且fork所在的saga不会在等待fn返回结果的时候被中间件暂停,相反,它在fn被调用时便会立即恢复执行。

let task = yield fork(saga, ...args.concat(action))

fork创建一个非阻塞的任务,同时拥有一个返回值task,代码表示const task = yield fork(saga,…args),task是一个具备着某些实用方法与属性的对象。一个task就想一个在后台运行的进程,在redux-saga应用程序中,可以运行多个task,task可通过fork函数创建。

cancel

cancel(task):cancel中参数tasks是可选的,如果传参,可以传入一个或多个task,像这样cancel(task)、cancel(task1,task2,…tasks) ,其中task是由fork指令返回的Task对象,用于取消这些task对应的fork分叉任务。其中如果期望在这些fork任务被取消时执行一些取消逻辑可以将这些取消逻辑放在finally区块中。任务被取消后,依然会执行finally中的代码。

cancel如果没有接受到传参,像这样yield cancel(),将取消该代码所在的任务,即自取消,如下代码,cancelTask任务将被自取消。

function* cancelTask() { 
   
    try { 
   
        // 1,取消当前任务cancelTask
        yield cancel()
        // 2,由于当前任务被取消,下方打印将不会执行
        console.log('我还能被执行吗?');
    } catch (error) { 
   
        console.log('error,cancelTask');
    } finally { 
   
        // 3,任务结束最后都会执行finally区块内代码,所以下面代码将执行
        console.log('我肯定可以被执行');
    }
}

all

all([...effects]): 命令中间件并行的运行多个effects,并等待其全部完成,返回全部effcts结果。相当于Promise.all的行为。

创建一个Effect,命令中间件并行地运行多个Effect,并等待他们全部完成,其中分为两种情况:

  1. all中任务全部完成,则all所在Generator恢复执行。
  2. 如果参数中某个任务失败且该任务未对错误进行处理,那么错误将冒泡到all所在的Generator中,且取消其他任务。

如果错误被catch处理,那么依然视为完成。不会取消其它任务。

而all可能是阻塞的也有可能是非阻塞的,这取决于all中创建Effect的形式,如果all参数中使用非阻塞的方法创建任务,那么all就不会阻塞all后面的代码,比如yield all ([call(task1),fork(task2)]),那么all就会被call(task1)阻塞,如果是yield all[fork(task1),fork(task2)] ,那么all就不会被阻塞。

一般根saga文件都会使用该方法,即同时启动所有该项目所需要运行的saga任务。

import { 
    all, put, takeEvery, delay, takeLatest } from 'redux-saga/effects'

function* rootSaga() { 
   
    // 启动delay(3000, 'Love U')任务,因为delay是阻塞任务,所以delayRes将在3s后才能接收到返回的'Love U'值
    const delayRes = yield all([delay(3000, 'Love U')])
    // 3s后输出['Love U']
    console.log(delayRes); 
}

export default rootSaga

all方法也可以传递对象。

function* allTask() { 
   
  // 1,task1返回值1000 task2返回值2000 task3返回值3000 
  const res = yield all({ 
   
      task1: call(task1),
      task2: call(task2),
      task3: call(task2)
  })
  // 2,res结果将是 {task1:1000,task2:2000,task3:3000}
  console.log(res);
}

案例1:错误处理了


import { 
    call, delay, all } from 'redux-saga/effects'

function* task1() { 
   
    try { 
   
        // 1.1,这里yield一个失败的Promise,这相当于抛出一个内容为1000的错误
        yield Promise.reject('1000')
        // 1.2,控制台不会输出'task1',因为1.1已经抛出一个错误,所以这段代码不会被执行
        console.log('task1');
    } catch (error) { 
   
        // 1.3,catch捕获 yield Promise.reject('1000') 抛出的错误,所以这里控制台将输出 `task1_error, 1000`
        console.log('task1_error', error)
    } finally { 
   
        // 1.4,task1因为抛错即将结束,结束之前都会执行finally区块中内容,所以控制台将输出'task1_finally'
        console.log('2:task1_finally');
        // 1.5,task1在finally中返回一个'task1 finished'字符串
        return 'task1 finished'
    }
}
function* task2() { 
   
    try { 
   
        // 1.6,task2任务延迟2s
        yield delay(2000)
        // 1.7,2s后控制台输出'task2'
        console.log('3:task2');
        // 1.8,task2返回字符串`task2 success`,但是后面finally区块中也有return语句,最终话return值以finally中return为准
        // 但console.log('`task2 success`');依然执行,只不过return结果替换成finally中的return结果
        return console.log('4:task2 success');
    } catch (error) { 
   
        // 1.9,因为task2中没有出现抛错,所以catch不会执行,所以下面代码不会执行
        console.log('1,task2_error', error)
    } finally { 
   
        // 1.91,task任务正常结束,执行finally区块,所以将输出'task2_finally'
        console.log('5:task2_finally');
        // 1.92,finally区块返回字符串'task2 finished',因为finally中return权重大于catch中return
        // 所以最终返回值以finally区块中返回值为准
        return 'task2 finished'
    }

}

function* rootSaga() { 
   
    // 1,根saga使用all并发启动阻塞任务task1与task2
    const res = yield all([call(task1), call(task2)])
    // 2,当all接受到结果时输出结果
    console.log('6:res:', res);
}

export default rootSaga

在这里插入图片描述

案例2:错误未处理

错误将冒泡到all所在的Generator中,且取消其他任务。如果被取消的任务有finally,那么依然会执行

// 当前路径:src/LearningSaga/saga/index.js

import { 
    call, delay, all } from 'redux-saga/effects'

function* task1() { 
   
    // 1.1,这里yield一个失败的Promise,这相当于reject一个内容为1000的错误
    yield Promise.reject('1000')
    // 1.2,因为1.1已经reject一个错误,所以这段代码不会被执行,同时task1未对错误进行处理,错误将冒泡到父函数
    console.log('task1');
}
function* task2() { 
   
    try { 
   
        // 1.3,因为task1已经失败,且错误未处理,所以task2任务将取消,try中代码将不会执行
        yield delay(2000)
        console.log('task2');
    } catch (error) { 
   

    } finally { 
   
        // 1.4,task2任务取消,执行finally,所以输出5:task2_finally
        console.log('1:task2_finally');
    }
}

function* rootSaga() { 
   
    try { 
   
        // 1,根saga使用all并发启动阻塞任务task1与task2
        const res = yield all([call(task1), call(task2)])
        // 2,当all接受到结果时输出结果,但是由于task1中错误未被捕获,所以错误冒泡到rootSaga中,所以下面代码将不会执行
        console.log('6:res:', res);
    } catch (error) { 
   
        // 3,rootSaga的catch捕获到task1,并输出结果
        console.log('2:task1冒泡到rootSaga中的错误,现在已经被rootSaga捕获');
    }

}

export default rootSaga

在这里插入图片描述

race

race方法类似于Promise.race,即race参数中多个任务竞赛,谁先完成,race就结束,这里也分两种情况:

  1. 如果率先完成者正常完成,则取消其他未完成的任务,且完成任务结果时该任务return值,其他取消任务的结果均为undefined。
  2. 率先完成任务失败(抛错且未处理),则错误冒泡到race所在Generator函数中,且取消其他竞赛中的任务。

race接收的参数除了是数组外,还可以是对象。

function* raceTask() { 
   
    // 1,task1返回值1000 task2返回值2000 task3返回值3000 
    const res = yield race({ 
   
        task1: call(task1),
        task2: call(task2),
        task3: call(task2)
    })
    //
    // 2,其中task2率先完成,所以res结果将是 {task1:undefined,task2:2000,task3:undefined}
    console.log(res);
}

案例1:错误处理了


import { 
    call, delay, race } from 'redux-saga/effects'

function* task1() { 
   
    try { 
   
        // 1.1,这里yield一个失败的Promise,这相当于抛出一个内容为1000的错误
        yield Promise.reject('1000')
        // 1.2,控制台输出'task1',因为1.1已经抛出一个错误,所以这段代码不会被执行
        console.log('task1');
    } catch (error) { 
   
        // 1.3,catch捕获 yield Promise.reject('1000') 抛出的错误,所以这里控制台将输出 `task1_error, 1000`
        console.log('1:task1_error', error)
    } finally { 
   
        // 1.4,task1因为抛错即将结束,结束之前都会执行finally区块中内容,所以控制台将输出'task1_finally'
        console.log('2:task1_finally');
        // 1.5,task1在finally中返回一个'task1 finished'字符串
        return 'task1 finished'
    }
}
function* task2() { 
   
    try { 
   
        // 1.6,task2任务延迟2s,因为这里是race,task1明显快于task2,所以task2任务将被取消
        // 所以在finally之前代码都不会执行
        yield delay(2000)
        console.log('task2');
    } catch (error) { 
   
        console.log('task2_error', error)
    } finally { 
   
        // 1.7,task2任务被取消,执行finally区块,所以将输出'task2_finally'
        console.log('3:task2_finally');
        // 1.8,因为task2任务被取消,其return结果不作为返回结果
        return 'task2 finished'
    }

}

function* rootSaga() { 
   
    // 1,根saga使用race竞赛启动阻塞任务task1与task2
    const res = yield race([call(task1), call(task2)])
    // 2,当race接受到结果时输出结果,因为task2被取消,所以其结果都为undefined,不管有没有return
    // 所以输出结果将是: ["task1 finished", undefined]
    console.log('4:res:', res);
}

export default rootSaga

在这里插入图片描述

案例2:错误未处理

// 当前路径:src/LearningSaga/saga/index.js

import { 
    call, delay, all } from 'redux-saga/effects'

function* task1() { 
   
    // 1.1,这里yield一个失败的Promise,这相当于reject一个内容为1000的错误
    yield Promise.reject('1000')
    // 1.2,因为1.1已经reject一个错误,所以这段代码不会被执行,同时task1未对错误进行处理,错误将冒泡到父函数
    console.log('task1');
}
function* task2() { 
   
    try { 
   
        // 1.3,因为task1已经失败,且错误未处理,所以task2任务将取消,try中代码将不会执行
        yield delay(2000)
        console.log('task2');
    } catch (error) { 
   

    } finally { 
   
        // 1.4,task2任务取消,执行finally,所以输出task2_finally
        console.log('1:task2_finally');
    }
}

function* rootSaga() { 
   
    try { 
   
        // 1,根saga使用all并发启动阻塞任务task1与task2
        const res = yield all([call(task1), call(task2)])
        // 2,当all接受到结果时输出结果,但是由于task1中错误未被捕获,所以错误冒泡到rootSaga中,所以下面代码将不会执行
        console.log('6:res:', res);
    } catch (error) { 
   
        // 3,rootSaga的catch捕获到task1,并输出结果
        console.log('2:task1冒泡到rootSaga中的错误,现在已经被rootSaga捕获');
    }
}

export default rootSaga

在这里插入图片描述

概念

阻塞调用/非阻塞调用

阻塞调用的意思是saga在yield Effect之后会等待其结果返回,结果返回后才会继续执行saga中下一个指令。非阻塞调用的意思是,saga会在yield Effect之后立即恢复执行。下面是一个阻塞调用与非阻塞调用的例子。

阻塞调用:下面代码中call是个会产生阻塞调用的方法。

import { 
    delay, call } from 'redux-saga/effects'

function* say() { 
   
    console.log(`${ 
     new Date().getSeconds()}:Hi ~`);
    yield delay(1000)
    console.log(`${ 
     new Date().getSeconds()}:I love u ~`);
    yield delay(1000)
}
function* rootSaga() { 
   
    // 这里yield 一个call Effect,该Effect作用是告诉中间件执行say
    // 由于call属于阻塞调用的方法,所以后面的 console.log 将等待say执行完毕 之后执行 
    yield call(say)
    console.log(`${ 
     new Date().getSeconds()}:I love u too !`);
}
export default rootSaga

在这里插入图片描述

非阻塞调用:fork属于非阻塞调用的方法

import { 
    delay, fork } from 'redux-saga/effects'

function* say() { 
   
    console.log(`${ 
     new Date().getSeconds()}:Hi ~`);
    yield delay(1000)
    console.log(`${ 
     new Date().getSeconds()}:I love u ~`);
    yield delay(1000)
}
function* rootSaga() { 
   
    // 这里yield 一个fork Effect,该Effect作用是告诉中间件执行say
    // 由于fork属于非阻塞调用,所以先执行say方法,遇到say中的阻塞部分将跳出say,执行 yield fork(say)后面的console.log
    // console.log执行完毕继续回到say中执行
    yield fork(say)
    console.log(`${ 
     new Date().getSeconds()}:I love u too !`);
}
export default rootSaga

在这里插入图片描述

实现takeEvery和takeLatest

import { 
    take, fork } from "redux-saga/effects";

// pattern:监听的action的type
// saga:监听到当前action所需要执行的saga函数
// args:其他交给takeEvery的参数
function takeEvery(pattern, saga, ...args) { 
   
    function* help() { 
   
        while (true) { 
   
            const action = yield take(pattern)
            yield fork(saga, ...args.concat(action))
        }
    }
    return fork(help)
}
import { 
    take, fork, cancel } from "redux-saga/effects";

// 自定义takeLatest
function* takeLatest(pattern, saga, ...args) { 
   
  function* help() { 
   
    let lastTask;
    while (true) { 
   
      const action = yield take(pattern);
      if (lastTask) yield cancel(lastTask);
      lastTask = yield fork(saga, ...args.concat(action));
    }
  }
  yield fork(help);
}

前端学习交流QQ群 862748629,群内学习讨论的氛围很好,大佬云集。点我加入

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/191720.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • python和java哪个好学-Python和Java对比,全面解读哪个语言最赚钱,前景最好?

    python和java哪个好学-Python和Java对比,全面解读哪个语言最赚钱,前景最好?都知道现在最火爆的是人工智能、大数据。而人工智能和大数据主要用的语言就是Java和Python。今天我们就来分析一下,当前java和python,哪个就业前景更好,薪资更高?该学哪一个?一、语言历史Python:生而为简Python是一门拥有简洁语法的高级编程语言。一个名为GuidovanRossum的荷兰大佬在1991年设计了它。Rossum设计这门语言的初衷,就是为了让代码读起来更轻松,并且…

  • 电脑加速 2010年最新方法

    电脑加速 2010年最新方法
    1、加快开关机速度

      XP中关机时,系统会发送消息到运行程序和远程服务器,告诉它们系统要关闭,并等待接到回应后系统才开始关机。加快开机速度,可以先设置自动结束任务,首先找到HKEY_CURRENT_USERControlPanelDesktop,把AutoEndTasks的键值设置为1;然后在该分支下有个“HungAppTimeout”,把它的值改为“4000(或更少),默认为50000;最后再找到HKEY_LOCAL_MACHINESystemCurrentContr

  • pycharm database 没有_pycharm社区版使用教程

    pycharm database 没有_pycharm社区版使用教程网上教程都是直接打开右上角的database,但是我死活也没找到,后来发现应该是因为社区版的问题,需要自己安装,详细步骤如下图。1.打开File—》Settings—-》Plugins搜索database,选择DatabaseNavigator安装即可done~

  • springboot连接多个数据库

    springboot连接多个数据库今天接到一个新的需求,需要把自己数据库某个表的数据迁移到别的数据库中,于是百度,中间出现了一些细节的问题,解决花了点时间,在此记录一下,下次避免出现过的错误这里把连接一个数据库的情况也记录一下,好做对比一、连接一个数据库1.启动类@SpringBootApplication//扫描mapper映射类所在路径@MapperScan(basePackages="com.xh….

  • 六、小程序|App抓包-移动端抓包app-抓包「建议收藏」

    六、小程序|App抓包-移动端抓包app-抓包「建议收藏」小程序|App抓包移动端抓包app-抓包 ———-IOS设备系统———一、IOS抓包IOS(thor+anubis)app应用市场下载即可:首次安装需要配置证书:抓包:简单测试抓取部分数据包:查看详细数据包内容:点击数据包查看详情:一直摁着,选择重放可进行重放测试thor跳转anubisanubis相关功能点和界面:重放记录:可修改重放:也可进行其他的导出操作:可以将数据包导出联合burp重放———-Android设备系统———二、android移动

  • 大数运算模板

    大数运算模板#include#include#include#includeusingnamespacestd;#defineMAX_DIGIT500//大数运算:加法intAdd(int*a,int*b,int*&result){if(a==NULL||b==NULL||re

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号