Redux-saga

Redux-saga

概述

redux-saga是一個(gè)用于管理redux應(yīng)用異步操作的中間件,redux-saga通過(guò)創(chuàng)建sagas將所有異步操作邏輯收集在一個(gè)地方集中處理,可以用來(lái)代替redux-thunk中間件。

  • 這意味著應(yīng)用的邏輯會(huì)存在兩個(gè)地方
    (1) reducer負(fù)責(zé)處理action的stage更新
    (2) sagas負(fù)責(zé)協(xié)調(diào)那些復(fù)雜或者異步的操作
  • sagas是通過(guò)generator函數(shù)來(lái)創(chuàng)建的
  • sagas可以被看作是在后臺(tái)運(yùn)行的進(jìn)程。sagas監(jiān)聽發(fā)起的action,然后決定基于這個(gè)action來(lái)做什么 (比如:是發(fā)起一個(gè)異步請(qǐng)求,還是發(fā)起其他的action到store,還是調(diào)用其他的sagas 等 )
  • 在redux-saga的世界里,所有的任務(wù)都通過(guò)用 yield Effects 來(lái)完成 ( effect可以看作是redux-saga的任務(wù)單元 )
  • Effects 都是簡(jiǎn)單的 javascript對(duì)象,包含了要被 saga middleware 執(zhí)行的信息
  • redux-saga 為各項(xiàng)任務(wù)提供了各種 ( Effects創(chuàng)建器 )
  • 因?yàn)槭褂昧薵enerator函數(shù),redux-saga讓你可以用 同步的方式來(lái)寫異步代碼
  • redux-saga啟動(dòng)的任務(wù)可以在任何時(shí)候通過(guò)手動(dòng)來(lái)取消,也可以把任務(wù)和其他的Effects放到 race 方法里以自動(dòng)取消

React+Redux Cycle(來(lái)源:https://www.youtube.com/watch?v=1QI-UE3-0PU)
  • produce: 生產(chǎn)
  • flow: 流動(dòng),排出
  • 整個(gè)流程:ui組件觸發(fā)action創(chuàng)建函數(shù) ---> action創(chuàng)建函數(shù)返回一個(gè)action ------> action被傳入redux中間件(被 saga等中間件處理) ,產(chǎn)生新的action,傳入reducer-------> reducer把數(shù)據(jù)傳給ui組件顯示 -----> mapStateToProps ------> ui組件顯示





安裝

yarn add redux-saga 
// cnpm install redux-saga -S

實(shí)例

(1) 配置

store.js


import {createStore,combineReducers, applyMiddleware} from 'redux';
import userNameReducer from '../username/reducer.js';
import createSagaMiddleware from 'redux-saga';       // 引入redux-saga中的createSagaMiddleware函數(shù)
import rootSaga from './saga.js';                    // 引入saga.js

const sagaMiddleware = createSagaMiddleware()        // 執(zhí)行

const reducerAll = {
    userNameReducer: userNameReducer
}


export const store = createStore(
    combineReducers({...reducerAll}),               // 合并reducer
    window.devToolsExtension ? window.devToolsExtension() : undefined,    // dev-tools
    applyMiddleware(sagaMiddleware)                 // 中間件,加載sagaMiddleware
)

sagaMiddleware.run(rootSaga)                        // 執(zhí)行rootSaga

(2) ui組件觸發(fā)action創(chuàng)建函數(shù)

username.js



import React from 'react';

export default class User extends React.Component {
    componentDidMount() {
        console.log(this.props)
    }
    go = () => {
        this.props.getAges(3)           // 發(fā)起action,傳入?yún)?shù)
    }
    render() {
        return (
            <div>
                這是username組件
                <div>
                    {
                        this.props.username.userNameReducer.username
                    }
                </div>
                <br/>
                <div onClick={this.go}>
                    點(diǎn)擊獲取提交age
                </div>
            </div>
        )
    }
}

(3) action創(chuàng)建函數(shù),返回action ----> 傳入saga ( 如果沒有saga,就該傳入reducer )

action.js



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

export function getAges(age) {
    console.log(age,'age') // 3
    return {
        type: actionTypes.GET_AGE,
        payload: age
    }
}

(4) saga.js ------> 捕獲action創(chuàng)建函數(shù)返回的action

saga.js




import { actionTypes } from '../username/constant.js';
import {call, put, takeEvery} from 'redux-saga/effects';     // 引入相關(guān)函數(shù)

function* goAge(action){    // 參數(shù)是action創(chuàng)建函數(shù)返回的action
    console.log(action)
    const p = function() {
        return fetch(`http://image.baidu.com/channel/listjson?rn=${action.payload}...`,{
            method: 'GET'
        })
        .then(res => res.json())
        .then(res => {
            console.log(res)
            return res
        })
    }
    const res = yield call(p)    // 執(zhí)行p函數(shù),返回值賦值給res

    yield put({      // dispatch一個(gè)action到reducer, payload是請(qǐng)求返回的數(shù)據(jù)
        type: actionTypes.GET_AGE_SUCCESS,
        payload: res   
    })
}

function* rootSaga() {     // 在store.js中,執(zhí)行了 sagaMiddleware.run(rootSaga)
    yield takeEvery(actionTypes.GET_AGE, goAge)   // 如果有對(duì)應(yīng)type的action觸發(fā),就執(zhí)行g(shù)oAge()函數(shù)
}

export default rootSaga;      // 導(dǎo)出rootSaga,被store.js文件import

(5) 然后由ui組件從reducer中獲取數(shù)據(jù),并顯示。。。







名詞解釋

Effect

一個(gè)effect就是一個(gè)純文本javascript對(duì)象,包含一些將被saga middleware執(zhí)行的指令。

  • 如何創(chuàng)建 effect ?
    使用redux-saga提供的 工廠函數(shù) 來(lái)創(chuàng)建effect
比如:

你可以使用  call(myfunc,  'arg1', 'arg2')  指示middleware調(diào)用  myfunc('arg1', 'arg2')

并將結(jié)果返回給 yield 了 effect  的那個(gè)  generator

Task

一個(gè) task 就像是一個(gè)在后臺(tái)運(yùn)行的進(jìn)程,在基于redux-saga的應(yīng)用程序中,可以同時(shí)運(yùn)行多個(gè)task

  • 通過(guò) fork 函數(shù)來(lái)創(chuàng)建 task
function* saga() {
  ...
  const task = yield fork(otherSaga, ...args)
  ...
}

阻塞調(diào)用 和 非組塞調(diào)用

  • 阻塞調(diào)用
    阻塞調(diào)用的意思是: saga 會(huì)在 yield 了 effect 后會(huì)等待其執(zhí)行結(jié)果返回,結(jié)果返回后才恢復(fù)執(zhí)行 generator 中的下一個(gè)指令
  • 非阻塞調(diào)用
    非阻塞調(diào)用的意思是: saga 會(huì)在 yield effect 之后立即恢復(fù)執(zhí)行

watcher 和 worker

指的是一種使用兩個(gè)單獨(dú)的saga來(lái)組織控制流的方式

  • watcher:監(jiān)聽發(fā)起的action 并在每次接收到action時(shí) fork 一個(gè) work
  • worker: 處理action,并結(jié)束它





api

createSagaMiddleware(...sagas)

createSagaMiddleware的作用是創(chuàng)建一個(gè)redux中間件,并將sagas與Redux store建立鏈接

  • 參數(shù)是一個(gè)數(shù)組,里面是generator函數(shù)列表
  • sagas: Array ---- ( generator函數(shù)列表 )

middleware.run(saga, ...args)

動(dòng)態(tài)執(zhí)行 saga。用于 applyMiddleware 階段之后執(zhí)行 Sagas。這個(gè)方法返回一個(gè)
Task 描述對(duì)象。

  • saga: Function: 一個(gè) Generator 函數(shù)
  • args: Array: 提供給 saga 的參數(shù) (除了 Store 的 getState 方法)

take(pattern)

----- 暫停Generator,匹配的action被發(fā)起時(shí),恢復(fù)執(zhí)行

創(chuàng)建一條 Effect 描述信息,指示 middleware 等待 Store 上指定的 action。 Generator 會(huì)暫停,直到一個(gè)與 pattern 匹配的 action 被發(fā)起。
pattern的規(guī)則
(1) pattern為空 或者 * ,將會(huì)匹配所有發(fā)起的action

(2) pattern是一個(gè)函數(shù),action 會(huì)在 pattern(action) 返回為 true 時(shí)被匹配
(例如,take(action => action.entities) 會(huì)匹配那些 entities 字段為真的 action)。

(3) pattern是一個(gè)字符串,action 會(huì)在 action.type === pattern 時(shí)被匹配

(4) pattern是一個(gè)數(shù)組,會(huì)針對(duì)數(shù)組所有項(xiàng),匹配與 action.type 相等的 action
(例如,take([INCREMENT, DECREMENT]) 會(huì)匹配 INCREMENT 或 DECREMENT 類型的 action)

take實(shí)例:

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))    // 執(zhí)行action.js中的getAges函數(shù)
            .then(res => {
                setTimeout(()=> {
                    console.log('5s鐘后才會(huì)執(zhí)行settimeout')
                    this.props.settimeout()
                },5000)           // 在getAges函數(shù)執(zhí)行完后,再過(guò)5s執(zhí)行,settimeout()函數(shù)
            }) 
        
        
    }
    render() {
        console.log(this.props, 'this.props')
        return (
            <div>
                這是username組件
                <br/>
                <div onClick={this.go}>
                    點(diǎn)擊獲取提交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中有對(duì)應(yīng)的actionTypes.GET_AGE
        payload: age
    }
}

export function settimeout() {
    return {
        type: actionTypes.MATCH_TAKE,  // 在saga中有對(duì)應(yīng)的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執(zhí)行到take時(shí),會(huì)暫停執(zhí)行,直到有type為MATCH_TAKE的action發(fā)起時(shí),才恢復(fù)執(zhí)行

    // 這里的效果就是點(diǎn)擊按鈕 5s鐘后 才顯示請(qǐng)求到的內(nèi)容,( 5s鐘后才執(zhí)行下面的put語(yǔ)句 )
    yield put({
        type: actionTypes.GET_AGE_SUCCESS,
        payload: res
    })
}

function* rootSaga() {
    yield takeEvery(actionTypes.GET_AGE, goAge)  
          
    // 有對(duì)應(yīng)的type是GET_AGE的action發(fā)起時(shí),執(zhí)行g(shù)oAge() Generator函數(shù)
}

export default rootSaga;



fork(fn, ...args)

----- 無(wú)阻塞的執(zhí)行fn,執(zhí)行fn時(shí),不會(huì)暫停Generator

----- yield fork(fn ...args)的結(jié)果是一個(gè) Task 對(duì)象

task對(duì)象 ---------- 一個(gè)具備某些有用的方法和屬性的對(duì)象

創(chuàng)建一條 Effect 描述信息,指示 middleware 以 無(wú)阻塞調(diào)用 方式執(zhí)行 fn。

  • fn: Function - 一個(gè) Generator 函數(shù), 或者返回 Promise 的普通函數(shù)

  • args: Array - 一個(gè)數(shù)組,作為 fn 的參數(shù)

  • fork 類似于 call,可以用來(lái)調(diào)用普通函數(shù)和 Generator 函數(shù)。但 fork 的調(diào)用是無(wú)阻塞的,在等待 fn 返回結(jié)果時(shí),middleware 不會(huì)暫停 Generator。 相反,一旦 fn 被調(diào)用,Generator 立即恢復(fù)執(zhí)行。

  • forkrace 類似,是一個(gè)中心化的 Effect,管理 Sagas 間的并發(fā)。
    yield fork(fn ...args) 的結(jié)果是一個(gè) Task 對(duì)象 —— 一個(gè)具備某些有用的方法和屬性的對(duì)象。

  • fork: 是分叉,岔路的意思 ( 并發(fā) )

實(shí)例:



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('該顯示會(huì)在獲得圖片后,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觸發(fā),才會(huì)恢復(fù)執(zhí)行

    yield fork(x)  // 無(wú)阻塞執(zhí)行,即x()generator觸發(fā)后,就會(huì)執(zhí)行下面的put語(yǔ)句

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

}

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

export default rootSaga;


join(task)

----- 等待fork任務(wù)返回結(jié)果(task對(duì)象)

創(chuàng)建一條 Effect 描述信息,指示 middleware 等待之前的 fork 任務(wù)返回結(jié)果。

  • task: Task - 之前的 fork 指令返回的 Task 對(duì)象
  • yield fork(fn, ...args) 返回的是一個(gè) task 對(duì)象

cancel(task)

創(chuàng)建一條 Effect 描述信息,指示 middleware 取消之前的 fork 任務(wù)。

  • task: Task - 之前的 fork 指令返回的 Task 對(duì)象
  • cancel 是一個(gè)無(wú)阻塞 Effect。也就是說(shuō),Generator 將在取消異常被拋出后立即恢復(fù)。

select(selector, ...args)

----- 得到 Store 中的 state 中的數(shù)據(jù)

創(chuàng)建一條 Effect 描述信息,指示 middleware 調(diào)用提供的選擇器獲取 Store state 上的數(shù)據(jù)(例如,返回 selector(getState(), ...args) 的結(jié)果)。

  • selector: Function - 一個(gè) (state, ...args) => args 函數(shù). 通過(guò)當(dāng)前 state 和一些可選參數(shù),返回當(dāng)前 Store state 上的部分?jǐn)?shù)據(jù)。

  • args: Array - 可選參數(shù),傳遞給選擇器(附加在 getState 后)

  • 如果 select 調(diào)用時(shí)參數(shù)為空( --- 即 yield select() --- ),那 effect 會(huì)取得整個(gè)的 state
    (和調(diào)用 getState() 的結(jié)果一樣)

重要提醒:在發(fā)起 action 到 store 時(shí),middleware 首先會(huì)轉(zhuǎn)發(fā) action 到 reducers 然后通知 Sagas。這意味著,當(dāng)你查詢 Store 的 state, 你獲取的是 action 被處理之后的 state。


put(action)

----- 發(fā)起一個(gè) action 到 store

創(chuàng)建一條 Effect 描述信息,指示 middleware 發(fā)起一個(gè) action 到 Store。

  • put 是異步的,不會(huì)立即發(fā)生

call(fn, ...args) 阻塞執(zhí)行,call()執(zhí)行完,才會(huì)往下執(zhí)行

----- 執(zhí)行 fn(...args)

----- 對(duì)比 fork(fn, ...args) 無(wú)阻塞執(zhí)行

創(chuàng)建一條 Effect 描述信息,指示 middleware 調(diào)用 fn 函數(shù)并以 args 為參數(shù)。

fn: Function - 一個(gè) Generator 函數(shù), 或者返回 Promise 的普通函數(shù)

args: Array - 一個(gè)數(shù)組,作為 fn 的參數(shù)

  • fn 既可以是一個(gè)普通函數(shù),也可以是一個(gè) Generator 函數(shù)

race(effects)

  • effects: Object : 一個(gè){label: effect, ...}形式的字典對(duì)象

同時(shí)執(zhí)行多個(gè)任務(wù)

當(dāng)我們需要 yield 一個(gè)包含 effects 的數(shù)組, generator 會(huì)被阻塞直到所有的 effects 都執(zhí)行完畢,或者當(dāng)一個(gè) effect 被拒絕 (就像 Promise.all 的行為)時(shí),才會(huì)恢復(fù)執(zhí)行Generator函數(shù) ( yield后面的語(yǔ)句 )。

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


// 正確寫法, effects 將會(huì)同步執(zhí)行
const [users, repos] = yield [
  call(fetch, '/users'),
  call(fetch, '/repos')
]


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

// 錯(cuò)誤寫法
const users = yield call(fetch, '/users'),
const repos = yield call(fetch, '/repos')

// call會(huì)阻塞執(zhí)行,第二個(gè) effect 將會(huì)在第一個(gè) call 執(zhí)行完畢才開始。



2018-6-25

throttle ------ 節(jié)流閥的意思

throttle(ms, pattern, saga, ...args)

  • 用途,是在處理任務(wù)時(shí),無(wú)視給定的時(shí)長(zhǎng)內(nèi)新傳入的 action。即在指定的時(shí)間內(nèi),只執(zhí)行一次 saga函數(shù)
    實(shí)例:

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內(nèi)新的操作將被忽略
}

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

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

export default rootSaga;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容