【redux】redux-saga学习笔记

前言

  • 写这玩意差点让我的惰性发挥到极致,不知道从哪里写好,搞得我都不想写。但是只能逼着自己一步步写才能进步。。。

redux-saga是什么

  • redux-saga本质上来说是redux的中间件,这里从上篇redux文章可以发现,redux的中间件是通过applyMiddleware实现的,这个函数本质是通过compose来组合一个复杂函数,改写store.dispatch。
  • reducer是一个纯函数,拿上一篇的reducer做例子:
function reducer1(state={count:5},action){//初始值分配到每个reducer上使用
    switch (action.type){
        case 'ADD1':
            return {
                ...state,count:state.count+action.payload
            };
        case 'MINUS1':
            return {
                ...state,count:state.count-action.payload
            };
    }
    return state
}
  • 它的返回结果只依赖于参数,不会产生额外的副作用。
  • 但在实际工作中,我们需要做一些异步操作(比如请求)或者不纯粹的操作(改变外部状态)。这些在函数式编程范式中被称为“副作用”。
  • redux-saga 就是用来处理副作用(异步任务)的一个中间件。它是一个接收事件,并可能触发新事件的过程管理者,为你的应用管理复杂的流程。

分类

  • worker saga 做实际的工作,如调用API,进行异步请求,获取异步封装结果。
  • watcher saga 监听被dispatch的actions,当接受到action或者知道其被触发时,调用worker执行任务。
  • root saga saga的唯一入口。

使用

  • 官网文档
  • 先使用create-react-app创建项目。
  • 此外还要装这些:
cnpm i redux react-redux redux-saga  --save
import React  from 'react';
import ReactDOM from 'react-dom'
import { createStore,applyMiddleware,combineReducers } from 'redux'
import createSagaMiddleware from 'redux-saga'
let sagaMiddleware=createSagaMiddleware()
function counter(state = {number:0}, action) {
    switch (action.type) {
      case 'ADD':
        return {...state,number:state.number+1}
      case 'MINUS':
        return {...state,number:state.number-1}
      default:
        return state
    }
  }
let combinereducer =combineReducers({
    counter
})
let store = applyMiddleware(sagaMiddleware)(createStore)(combinereducer)
function *rootsaga(){
    console.log('yehuozhili');
}
sagaMiddleware.run(rootsaga)
ReactDOM.render(<p>cxzxc</p>,document.getElementById('root'))
  • 启动项目,控制台有输出就可以了。
  • 然后进行修改,做一个counter组件,实现加一功能。

Counter.js

import React from 'react'
import {connect}from 'react-redux'
import actions from './actions'
function Counter(props) {
    return (
        <div>
        <div>{props.number}</div>
            <button onClick={()=>props.add()}>+</button>
        </div>
    )
}
export default connect(state=>state.counter, actions)(Counter)

actions.js

export default{
    add(){
        return {type:'ADD'}
    }
}

index.js

import React  from 'react';
import ReactDOM from 'react-dom'
import { createStore,applyMiddleware,combineReducers } from 'redux'
import createSagaMiddleware from 'redux-saga'
import Counter from './counter'
import {Provider} from 'react-redux'
let sagaMiddleware=createSagaMiddleware()
function counter(state = {number:0}, action) {
    switch (action.type) {
      case 'ADD':
        return {...state,number:state.number+1}
      case 'MINUS':
        return {...state,number:state.number-1}
      default:
        return state
    }
  }
let combinereducer =combineReducers({
    counter
})
let store = applyMiddleware(sagaMiddleware)(createStore)(combinereducer)
function *rootsaga(){
    console.log('yehuozhili');
}
sagaMiddleware.run(rootsaga)
ReactDOM.render(
    <Provider store = {store}>
    <Counter></Counter>
    </Provider>
,document.getElementById('root'))
  • 点击按钮可以正常加一即可。
  • 下面就实现异步加一。正常来说,action里只能返回一个对象,如果用thunk中间件,可以返回函数,从而能实现异步。但是saga依然让action返回对象,派发的动作并没有传给reducer,而是被中间件拦截,在中间件中进行执行。中间件中会有一些effect,其中put可以派发action给reducer。
  • 这里逻辑就是这样:在saga中间件中监听异步加一的action,监听到后执行workersaga,也就是我们自定义的一个generator函数,这个函数用来代替以前需要改写reducer里对action的响应。在这个函数里,使用delay进行延迟,相当于一些异步操作,然后用put派发加一的action给reducer,从而实现异步加一。

actions.js

export default{
    add(){
        return {type:'ADD'}
    },
    delayAdd(){
        return {type:'DELAYADD'}
    }
}

index.js

import React  from 'react';
import ReactDOM from 'react-dom'
import { createStore,applyMiddleware,combineReducers } from 'redux'
import createSagaMiddleware from 'redux-saga'
import Counter from './counter'
import {Provider} from 'react-redux'
import {takeEvery,delay,put,all} from 'redux-saga/effects'

let sagaMiddleware=createSagaMiddleware()
function counter(state = {number:0}, action) {
    switch (action.type) {
      case 'ADD':
        return {...state,number:state.number+1}
      case 'MINUS':
        return {...state,number:state.number-1}
      default:
        return state
    }
  }
let combinereducer =combineReducers({
    counter
})
let store = applyMiddleware(sagaMiddleware)(createStore)(combinereducer)
function * workerADD(){//workersaga 有点像reducer的逻辑。实际是被中间件拦截后走的逻辑。
    yield delay(1000)
    yield put({type:'ADD'})
}
function * watcherDelayAdd(){//watchersaga 用来监听action
    yield takeEvery('DELAYADD',workerADD)//监听action,监听到执行worker
}
function *rootsaga(){//入口saga
    yield all([watcherDelayAdd()])//all里的saga会全部执行
}
sagaMiddleware.run(rootsaga)
ReactDOM.render(
    <Provider store = {store}>
    <Counter></Counter>
    </Provider>
,document.getElementById('root'))

counter.js

import React from 'react'
import {connect}from 'react-redux'
import actions from './actions'
function Counter(props) {
    return (
        <div>
        <div>{props.number}</div>
            <button onClick={()=>props.add()}>+</button>
            <button onClick={()=>props.delayAdd()}>异步+</button>
        </div>
    )
}
export default connect(state=>state.counter, actions)(Counter)
  • 实际项目中,每个模块有自己的watcher和worker,导出后,最后统一写在总的rootsaga的all里。
  • 还有个传参,workersaga会收到被拦截的action,所以参数只要让action携带,就可以传到worker去。
function * workerADD(action){
    yield delay(1000)
    yield put({type:'ADD'})
}
  • 通常在generator里,yield会阻塞执行,这个监听实际上是在run的时候就已经完成了的。可以试试改变监听的watchersaga,让它停5秒再监控,会发现在载入页面时候确实停了5秒,而且这个时候去点异步加一无效。等下面的sdsa打印在控制台后,点击异步加一会生效。
function * watcherDelayAdd(){
    console.log('xxzc');
    yield delay(5000)
     yield   takeEvery('DELAYADD',workerADD)//监听action,监听到执行worker
    console.log('sdsa');
}
  • 这个意思就是watcher的监控saga会一开始把逻辑全都做好,实际点异步加一并不会走watcher,而直接走worker。
  • saga里每个effect里全都是返回一个对象。在run里会对这些对象进行判断和操作。
  • 下面实现异步加三次的操作,实际只要把监听函数改成下面即可:
function * watcherDelayAdd(){
    yield   take('DELAYADD')//监听action,监听到执行worker 
    yield workerADD()
    yield   take('DELAYADD')//监听action,监听到执行worker 
    yield workerADD()
    yield   take('DELAYADD')//监听action,监听到执行worker 
    yield workerADD()
}
  • 实际工作中会有登录注销,我们对其稍微改一下,模拟登录和注销:
function mylogin (user){
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            let token = 11
            resolve(token)
        }, 1000);
    })
}
function * workerADD(action){
    let token = yield call(mylogin,action.payload)
    return token 
}
function * watcherDelayAdd(){
    while(true){
        let action =yield take('LOGIN')
        let token = yield workerADD(action)
        if(token){
            yield put({type:'ADD'})//登录成功,加一
            yield take('MINUS')//登录状态退出,减一
        }
    }
  • 这个watcher里面写个while true可以发现,每次退出之后还可以登录,不加的话,注销后就不能登录了,只能登录一次。

  • 异步调用和取消任务:

function mylogin (user){
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            let token = 11
            resolve(token)
        }, 2000);
    })

}
function * workerADD(action){
    let token = yield call(mylogin,action.payload)
    if(token){
        yield put({type:'ADD'})//登录成功,加一
        yield take('MINUS')//登录状态退出,减一
    }
}
function * watcherDelayAdd(){
    while(true){
        let action =yield take('LOGIN')
        let task = yield fork(workerADD,action)
        action = yield take(['LOGIN','MINUS'])//
        if(action.type==='MINUS'){//在异步发生时,监听到其中一个action,
            yield cancel(task)//对其处理,可以取消这个异步任务
        }
    }
}
  • 这里需要fork这个非阻塞effect,既然异步,所以没有返回值,只会返回task,通过task可以取消这个任务。
  • 实际项目中,有很多需要取消任务的情况,比如提交个表单会拉取数据,在等待期间用户不想等了,需要修改下再提交,就可以这么做。
  • 另外,task里失败的action也可以放到数组里,然后走后续逻辑,形成闭环。在请求前后,可以设置个flag,这个可以自由发挥。
  • 下面利用race,做个数字一直加,监听到特定action后停止:
function * workerADD(){
    while(true){
        yield delay(1000)
        yield put({type:'ADD'})
    }
}
function * watcherDelayAdd(){
    yield race( [ call(workerADD),take('MINUS')])
}
  • 这个race可以放数组或者对象,会让里面东西全部执行,在worker里由于死循环,所以就一直在加一,而另一个take,则是监听minus的action,一旦触发这个action,则其为胜利者,另一个由于死循环无法胜出,就会被取消执行。
发布了163 篇原创文章 · 获赞 9 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/yehuozhili/article/details/103979949