Redux-Saga基本使用

本文着重讲解Redux-Saga基本的使用,同时需要您有一些ES6_Generator/Redux/React-Redux/React相关知识。相关安装包有:redux,react-redux,redux-saga。

1,Redux-Saga简述:

redux-saga是redux的中间件,主要负责处理从action派发到更新store装填中间具有副作用行为的处理。

2,开始使用Redux-Saga

我们将使用create-react-app创建一个我们自己的react应用,该应用行为即点击按钮,页面上的数据完成+1行为。

  • 下面是react的入口文件,我们使用了redux,react-redux进行状态管理,并使用了redux-saga中间件。

        // 当前路径:src/index.js
    
        // 第三方模块引入
        import React from 'react'  
        import ReactDom from 'react-dom'
        import { Provider } from 'react-redux'
        import { createStore, applyMiddleware } from 'redux'
        import createSagaMiddleware from 'redux-saga'
    
        // 自定义模块引入
        // 1,redux中的 reducer引入
        import rootReducer from './LearningSaga/reducer'
        // 2,react中 Counter组件引入
        import Counter from './LearningSaga/component'
        // 3, redux-saga中间件的 saga文件引入
        import rootSaga from './LearningSaga/saga'
    
        // 4,创建一个redux-saga中间件
        const sagaMiddleware = createSagaMiddleware()
        // 5,将redux-saga中间件加入到redux中
        const store = createStore(rootReducer, {}, applyMiddleware(sagaMiddleware))
        // 6,动态的运行saga,注意 sagaMiddleware.run(rootSaga) 只能在applyMiddleware(sagaMiddleware)之后进行
        sagaMiddleware.run(rootSaga)
    
        // 7,挂载react组件
        ReactDom.render(<Provider store={store}><Counter /></Provider>, document.getElementById('root'))
    
    复制代码
  • 注释1是我们要用到的reducer,下面是reducer文件内容,很简单,我们创建了counterReducer,并使用redux提供的combineReducers处理并返回出去,熟悉redux使用的话,这里应该不用太多赘述,就是一个简单的对state.counter进行递增处理的reducer。

    // 当前路径:src/LearningSaga/reducer/index.js
    
    import { combineReducers } from 'redux'
    
    function counterReducer(state = 1, action = {}) {
        switch (action.type) {
            case "increment": return state + 1;
            default: return state
        }
    }
    
    const rootReducer = combineReducers({ counter: counterReducer })
    
    export default rootReducer
    复制代码
  • 注释2是我们react挂载的一个Counter组件,下面是Counter组件代码,也是个很简单的组件,其中使用了react-redux对Counter进行处理,这样就能在this.props中获取到redux中的状态以及dispatch方法。

    // 当前路径:src/LearningSaga/component/index.js
    
    import React from 'react'
    import { connect } from 'react-redux'
    
    class Counter extends React.Component {
        // 派发一个type为 increment_saga 的action
        add = () => this.props.dispatch({ type: 'increment_saga' })
        render() {
            return (
                <div>
                    <span>{this.props.counter}</span>
                    <button onClick={this.add}>add1-sync</button>
                </div>
            )
        }
    }
    const mapStateToProps = state => ({ counter: state.counter })
    export default connect(mapStateToProps)(Counter)
    复制代码
  • 注释4567完成了对redux创建,redux中redux-saga中间件的使用,以及Counter组件的挂载等。当然这里redux-saga中间件处理方式可能和其它中间件方式略有不同。

  • 在讲注释3之前我们先回顾一下Counter组件的使用,首先Counter组件渲染出store(redux)中counter数据和一个递增按钮add1-sync,点击该按钮将派发一个类型为increment_saga的action。我们期待的事 是该动作派发后,store中的counter数据能够加1,其实如果仅是如此,我们派发一个类型为increment的action就可以做到,因为我们在注释1的reducer中已经实现了对类型为increment的action处理。但我们这里要用redux-saga中间件替我们完成,我们可以想一下redux-saga中间件要完成这个加1行为大概要做怎样一件事,首先,拦截到action,然后做一些处理,最后更新store中的counter数据完成+1。没错,redux-saga中间件的流程就是这样,所以按照这个流程回头看,这里我们派发一个类型为increment_saga的action,redux-saga中间件获取到该action,因为这个行为很纯粹就是+1,redux-saga中间件再继续调用dispatch({type:'increment'})完成对store中counter数据+1。

  • 现在回到注释3,这是redux-saga中saga文件,下面是该文件的代码,我们看到该文件中的函数全是generator函数,没错,redux-saga的世界中,就是通过这些generator函数完成dispatch过程中副作用的处理(当然我们现在这个+1例子中还没涉及到一些强烈具有副作用的行为),这些generator函数我们叫它saga。saga中yield 后面的内容我们称呼它为Effects(redux-saga的任务单元),在Effects中我们可以进行启动其它saga,也可以处理一些副作用操作。

    // 当前路径:src/LearningSaga/saga/index.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
    复制代码
    • 这里我们导出的是一个rootSaga函数,该函数内容很简单,只有一行 yield all([watchIncrement()]),其中all是redux-saga所提供的一个方法,用来启动一个或多个Effects,这里我们只启动了一个watchIncrement的saga。

    • 继续看watchIncrement这个saga,函数内容仅有一行yield takeEvery('increment_saga', increment),其中takeEvery是redux-saga提供的一个方法,该方法传入两个参数(type,saga),其中type即对应action中的type,而saga则是redux-saga中间件匹配到对应type的action时需要启动的saga。所以这行代码的作用很简单,就是监听action类型为increment_saga,并启动对应saga进行处理该action。 现在假设watchIncrement监听到类型为increment_saga的动作,启动increment这个saga进行处理。我们进入increment函数中看看做了什么。

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

    • 现在整个saga文件已经介绍完它的作用,我们来复盘从Counter组件点击add1-sync按钮那一刻到最后store中的counter数据加1的流程。

      • 1,react入口文件中注释6启动了rootSaga,rootSaga执行yield all([watchIncrement()]),即启动了watchIncrement这个saga,并等待它运行完成

      • 2,watchIncrement的作用在前面说了,是监听类型为increment_saga的action,如果监听到,则启动increment这个saga对action进行进一步处理。

      • 3,现在Counter组件中add1-sync按钮点击,派发一个类型为increment_saga的动作。

      • 4,watchIncrement这个saga监听到该动作(type:’increment_saga’),启动increment对该action进行处理。

      • 5,increment中通过 yield put({ type: 'increment' }) 派发一个类型为increment的action出去

      • 6,reducer接受到类型为increment的action,执行对应的更新行为,完成store中counter数据+1的过程。

      • 7,最后更新Counter组件中的this.props.counter数据(这个自动更新行为由react-redux替我们完成)。

3,带有副作用的counter数据更新

我们将在原Counter组件 基础上增加一个add1-async按钮,点击该按钮将在1s后对counter数据进行+1

  • 在原Counter组件上新增add1-async按钮

    // 当前路径:src/LearningSaga/component/index.js
    
    import React from 'react'
    import { connect } from 'react-redux'
    
    class Counter extends React.Component {
        add = () => this.props.dispatch({ type: 'increment_saga' })
        // addAsync函数将派发一个类型为incrementAsync_saga的action
        addAsync = () => this.props.dispatch({ type: 'incrementAsync_saga' })
        render() {
            return (
                <div>
                    <span>{this.props.counter}</span>
                    <button onClick={this.add}>add1-sync</button>
                    <button onClick={this.addAsync}>add1-async</button>
                </div>
            )
        }
    }
    const mapStateToProps = state => ({ counter: state.counter })
    export default connect(mapStateToProps)(Counter)
    复制代码
  • 我们也要在saga文件中添加incrementAsync_saga动作对应的saga进行处理

    // 当前路径:src/LearningSaga/saga/index.js
    
    import { all, put, takeEvery, 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' })
    }
    function* watchIncrement() {
        yield takeEvery('increment_saga', increment) // 监听类型为increment_saga的action,监听到启动increment
    
        // 监听类型为incrementAsync_saga的action,监听到启动incrementAsync
        yield takeEvery('incrementAsync_saga', incrementAsync)
    }
    function* rootSaga() {
        yield all([watchIncrement()]) // 启动watchIncrement
    }
    
    export default rootSaga
    复制代码
    • 我们在watchIncrement添加了新的一行代码 yield takeEvery('incrementAsync_saga', incrementAsync),上一节中学习我们知道,这行代码作用是 监听类型为incrementAsync_saga的action,监听到启动incrementAsync。

    • 继续看incrementAsync,相较于之前的increment,多了一行代码 yield delay(1000),其中delay是redux-saga提供的延迟函数,该行代码表示延迟1s后才可以继续处理之后的代码。其本质可以看做这么个函数。

      // delay 相当于这里的 delay_
      function delay_(timeout) {
          return new Promise(r => {
              setTimeout(() => r(), timeout);
          })
      }
      复制代码
    • 所以延迟1s后,继续执行 yield put({ type: 'increment' }),即相当于dispatch({ type: 'increment' }) 完成counter更新。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享