redux-saga细说

参考:
https://blog.csdn.net/sinat_17775997/article/details/103524043
https://www.jianshu.com/p/6f96bdaaea22
redux-saga 中常见的几种模式(翻译)https://www.jianshu.com/p/c3425f9ef6b7

take([...]) 和 race 的比较重要的语境区别是:

take([...]) 是等待第一个相匹配的action的到达
race 是等待第一个 racing-effect 的完成

1.概述

Redux-saga是一个用于管理 Redux 应用异步操作的中间件(又称异步action)

本质都是为了解决异步action的问题

Redux Saga可以理解为一个和系统交互的常驻进程,这个线程可以通过正常的Redux Action从主应用程序启动,暂停取消,它能访问完整的Redux state,也可以dispatch Redux Action。 一个 Saga 就像是应用程序中一个单独的线程,它独自负责处理副作用。

其中,Saga可简单定义如下的公式:

Saga = Worker + Watcher

2.简单使用

redux-saga本质是一个可以自执行的generator。
在 redux-saga 中,UI 组件自身从来不会触发任务,它们总是会 dispatch 一个 action 来通知在 UI 中哪些地方发生了改变,而不需要对 action 进行修改。redux-saga 将异步任务进行了集中处理,且方便测试
所有的东西都必须被封装在 sagas 中。sagas 包含3个部分,用于联合执行任务:
1.worker saga 做所有的工作,如调用 API,进行异步请求,并且获得返回结果
2.watcher saga监听被 dispatch 的 actions,当接收到 action 或者知道其被触发时,调用 worker saga 执行任务
3.root saga立即启动 sagas 的唯一入口

特别提醒: redux-saga是通过ES6中的generator实现的。

创建一个redux-saga例子

  • 安装npm install redux-saga –g;
  • 使用createSagaMiddleware方法创建saga的sagaMiddleware
  • 在创建的redux的store时,使用applyMiddleware函数将创建的saga Middleware实例绑定到store上
  • 最后需要运行sagaMiddleware

创建一个hellosaga.js文件

export function * helloSaga() {
  console.log('Hello Sagas!');
}

在redux项目中使用redux-saga中间件

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { createStore, applyMiddleware,combineReducers } from 'redux';
import rootReducer from './reducers';
import { composeWithDevTools } from 'redux-devtools-extension';
import createSagaMiddleware from 'redux-saga';
import { Provider } from 'react-redux';
import { watchIncrementAsync } from './sagas/counter';
import { helloSaga } from './sagas'

//====1 创建一个saga中间件
const sagaMiddleware = createSagaMiddleware();

//====2 创建store
const store = createStore(
  rootReducer,
  composeWithDevTools(
    applyMiddleware(sagaMiddleware)
  )
);
//==== 3动态执行saga,注意:run函数只能在store创建好之后调用
sagaMiddleware.run(helloSaga);

ReactDOM.render(
  <Provider store={ store }>
    <App />
  </Provider>,
  document.getElementById('root')
);

这样代码跑起来,就可以看到控制台输出了Hello Saga

和调用redux的其他中间件一样,如果想使用redux-saga中间件,那么只要在applyMiddleware中调用一个createSagaMiddleware的实例。唯一不同的是需要调用run方法使得generator可以开始执行。

3.运行流程图

由上图可以看书saga主要做的了三件事

  • 监听用户发出的Action。
  • 发现用户发出的Action是自己当前的Action,然后做一些副作用(派发一个新的任务)。
  • store接收到新的任务,返回新的state。

4.核心AIP

tackEvery

监听action,每监听到一个action,就执行一次操作
允许多个请求同时执行,不管之前是否还有一个或多个请求尚未结束。

import { takeEvery } from 'redux-saga'

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

// 监听到Action为incrementAsync就会出发incrementAsync函数
function* watchIncrementAsync() {
    yield takeEvery('incrementAsync', incrementAsync)
}

// 注意watchIncrementAsync这个函数必须在主入口index中运行sagaMiddleware.run(watchIncrementAsync);

takeLatest

监听action,监听到多个action,只执行最近的一次
作用同takeEvery一样,唯一的区别是它只关注最后,也就是最近一次发起的异步请求,如果上次请求还未返回,则会被取消。

function* watchIncrementAsync() {
  yield takeLatest('incrementAsync', fetchData)
}

call

异步阻塞调用
下面fork的部分说道fork 类似于 call,可以用来调用普通函数和 Generator 函数
用来调用异步函数,将异步函数和函数参数作为call函数的参数传入,返回一个js对象。saga引入他的主要作用是方便测试,同时也能让我们的代码更加规范化。
和js原生的call一样,call函数也可以指定this对象,只要把this对象当第一个参数传入call方法就

const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

function* fetchData() { 
    // 2秒后打印saga(阻塞)
    // yield delay(2000);
    yield call(delay,2000); 
    console.log('saga'); 
}

// 加了call和不加效果是一样的,saga引入他的主要作用是方便测试,同时也能让我们的代码更加规范化。

fork

异步非阻塞调用,无阻塞的执行fn,执行fn时,不会暂停Generator
非阻塞任务调用机制:上面我们介绍过call可以用来发起异步操作,但是相对于 generator 函数来说,call 操作是阻塞的,只有等 promise 回来后才能继续执行,而fork是非阻塞的 ,当调用 fork 启动一个任务时,该任务在后台继续执行,从而使得我们的执行流能继续往下执行而不必一定要等待返回。

const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

function* fetchData() { 
    // 不用等待2秒,直接可以打印出saga,并发执行
    yield fork(delay,2000); 
    console.log('saga'); 
}

fork(fn, …args)

----- 无阻塞的执行fn,执行fn时,不会暂停Generator
----- yield fork(fn …args)的结果是一个 Task 对象
task对象 ---------- 一个具备某些有用的方法和属性的对象
创建一条 Effect 描述信息,指示 middleware 以 无阻塞调用 方式执行 fn。
fn: Function - 一个 Generator 函数, 或者返回 Promise 的普通函数
args: Array - 一个数组,作为 fn 的参数
fork 类似于 call,可以用来调用普通函数和 Generator 函数。但 fork 的调用是无阻塞的,在等待 fn 返回结果时,middleware 不会暂停 Generator。 相反,一旦 fn 被调用,Generator 立即恢复执行。
fork 与 race 类似,是一个中心化的 Effect,管理 Sagas 间的并发。
yield fork(fn ...args) 的结果是一个 Task 对象 —— 一个具备某些有用的方法和属性的对象。
fork: 是分叉,岔路的意思 ( 并发 )

实例:

import { actionTypes } from '../username/constant.js';
import {call, put, takeEvery, take, fork} from 'redux-saga/effects';

function* goAge(action){

    function* x() {
        yield setTimeout(() => {
           console.log('该显示会在获得图片后,2s中后显示') 
        }, 2000);
    }

    const p = function() {
        return fetch(`http://image.baidu.com/channel/listjson?pn=0&rn=${action.payload}`,{
            method: 'GET'
        })
        .then(res => res.json())
        .then(res => {
            console.log(res)
            return res
        })
    }
    const res = yield call(p)

    yield take(actionTypes.MATCH_TAKE)   // 阻塞,直到匹配的action触发,才会恢复执行

    yield fork(x)  // 无阻塞执行,即x()generator触发后,就会执行下面的put语句

    yield put({
        type: actionTypes.GET_AGE_SUCCESS,
        payload: res
    })

}

function* rootSaga() {
    yield takeEvery(actionTypes.GET_AGE, goAge)
}

export default rootSaga;

put

相当于dispatch,分发一个action

yield put({ type: 'incrementAsync'})

select

相当于getState,用于获取store中相应部分的state

function* incrementAsync(action) {
  let state = yield select(state => console.log('-----',state))
}

take

监听action,暂停Generator,匹配的action被发起时,恢复执行。

export function* watchIncrementAsync() {
   while(true){
     yield take('INCREMENT_ASYNC'); // 监听
     yield fork(incrementAsync);
  }
  // yield takeLatest(INCREMENT_ASYNC, incrementAsync); //takeLatest
}
    `take(pattern)`

----- 暂停Generator,匹配的action被发起时,恢复执行
创建一条 Effect 描述信息,指示 middleware 等待 Store 上指定的 action。 Generator 会暂停,直到一个与 pattern 匹配的 action 被发起。
pattern的规则
(1) pattern为空 或者 * ,将会匹配所有发起的action

(2) pattern是一个函数,action 会在 pattern(action) 返回为 true 时被匹配
(例如,take(action => action.entities) 会匹配那些 entities 字段为真的 action)。

(3) pattern是一个字符串,action 会在 action.type === pattern 时被匹配

(4) pattern是一个数组,会针对数组所有项,匹配与 action.type 相等的 action
(例如,take([INCREMENT, DECREMENT]) 会匹配 INCREMENT 或 DECREMENT 类型的 action)

take实例:

username.js

import React from 'react';


export default class User extends React.Component {
    go = () => {
        new Promise((resolve,reject) => {
            resolve(3)
        }).then(res => this.props.getAges(res))    // 执行action.js中的getAges函数
            .then(res => {
                setTimeout(()=> {
                    console.log('5s钟后才会执行settimeout')
                    this.props.settimeout()
                },5000)           // 在getAges函数执行完后,再过5s执行,settimeout()函数
            }) 
        
        
    }
    render() {
        console.log(this.props, 'this.props')
        return (
            <div>
                这是username组件
                <br/>
                <div onClick={this.go}>
                    点击获取提交age
                </div>
                <div>
                    {
                        this.props.username && 
                        this.props.username.userNameReducer.image.data && 
                        this.props.username.userNameReducer.image.data.map(
                            (item,key) => <div key={key}>{item.abs }</div>
                        )
                    }
                </div>
            </div>
        )
    }
}

action.js

import { actionTypes } from './constant.js';


export function getAges(age) {
    console.log(age,'age')
    return {
        type: actionTypes.GET_AGE,   // 在saga中有对应的actionTypes.GET_AGE
        payload: age
    }
}

export function settimeout() {
    return {
        type: actionTypes.MATCH_TAKE,  // 在saga中有对应的actionTypes.MATCH_TAKE,
    }
}

saga.js

import { actionTypes } from '../username/constant.js';
import {call, put, takeEvery, take} from 'redux-saga/effects';

function* goAge(action){
    console.log(action)
    const p = function() {
        return fetch(
            `http://image.baidu.com/channel/listjson?pn=0&rn=${action.payload}`,{
            method: 'GET'
        })
        .then(res => res.json())
        .then(res => {
            console.log(res)
            return res
        })
    }
    const res = yield call(p)
    yield take(actionTypes.MATCH_TAKE)   

    // generator执行到take时,会暂停执行,直到有type为MATCH_TAKE的action发起时,才恢复执行

    // 这里的效果就是点击按钮 5s钟后 才显示请求到的内容,( 5s钟后才执行下面的put语句 )
    
    yield put({
        type: actionTypes.GET_AGE_SUCCESS,
        payload: res
    })
}

function* rootSaga() {
    yield takeEvery(actionTypes.GET_AGE, goAge)  
          
    // 有对应的type是GET_AGE的action发起时,执行goAge() Generator函数
}

export default rootSaga;

cancel(task)

task: Task - 之前的 fork 指令返回的 Task 对象
cancel 是一个无阻塞 Effect。也就是说,Generator 将在取消异常被抛出后立即恢复。

作者:省局到
链接:https://www.jianshu.com/p/6f96bdaaea22
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

race([…effects])

创建一个Effect描述信息,指示 middleware 在多个 Effect 之间运行一个 race(与 Promise.race([…]) 的行为类似)。
race可以取到最快完成的那个结果,常用于请求超时

all([]…effects)

创建一个 Effect 描述信息,指示 middleware 并行运行多个 Effect,并等待它们全部完成。这是与标准的Promise#all相对应的 API。

import { call } from 'redux-saga/effects'
// 正确写法, effects 将会同步执行
const [userInfo, repos] = yield [
  call(fetch, '/users'),
  call(fetch, '/repos')
];

// 这两个请求是并行的
同时执行多个任务
当我们需要 yield 一个包含 effects 的数组, generator 会被阻塞直到所有的 effects 都执行完毕,或者当一个 effect 被拒绝 (就像 Promise.all 的行为)时,才会恢复执行Generator函数 ( yield后面的语句 )。


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


// 正确写法, effects 将会同步执行
const [users, repos] = yield [
  call(fetch, '/users'),
  call(fetch, '/repos')
]


---------------------------------------------------------------------

// 错误写法
const users = yield call(fetch, '/users'),
const repos = yield call(fetch, '/repos')

// call会阻塞执行,第二个 effect 将会在第一个 call 执行完毕才开始。

throttle ------ 节流阀的意思
throttle(ms, pattern, saga, …args)

用途,是在处理任务时,无视给定的时长内新传入的 action。即在指定的时间内,只执行一次 saga函数
实例:

import {put, call, takeEvery, select, fork, all, throttle } from 'redux-saga/effects';
import axios from 'axios';


const request = function(data) {
    return axios('/channel/listjson?pn=0&rn=30&tag1=明星&tag2=全部&ie=utf8', {
        params: data
    });
}


const getSagaImage = function* (data) {
    try {
        const res = yield call(request, data);
        const datas = res.data.data ?  res.data.data : null
        yield put({
            type: 'GET_IMAGE_SUCCESS',
            payload: datas
        });
        const uuid = yield select(state => state.user.username);
        console.log(uuid, 'uuid')
    } catch (err) {
        alert(err.message)
    }
}



const watchThrottle = function* () {
    yield throttle(3000, 'GET_IMAGES', getSagaImage);   // 3s内新的操作将被忽略
}

// const rootSaga =  function* () {
//     yield takeEvery('GET_IMAGES', getSagaImage)
// };

const rootSaga = function* () {
    yield all([
        fork(watchThrottle),
    ])
}

export default rootSaga;
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值