redux-saga基础知识

53 阅读5分钟

1.Redux-saga

  • redux-saga和redux-thunk一样,是一个Redux中获取存储异步数据的中间件
  • redux-saga可以直接拦截dispatch派发的action,从而实现在执行reducer之前执行一些其他操作

2.如何使用Redux-saga

redux-saga

redux-saga-ch

安装

$ npm install redux-saga

使用

配置

image.png

store/index.js

import { createStore, applyMiddleware } from "redux"
import reducer from "./reducer"
import MySaga from "./saga"
/**
 * redux-thunk
    import thunkMiddleware from "redux-thunk"
    
    // 创建store之前,通过applyMiddleware方法,告诉Redux需要引用哪些中间件
    const storeEnhancer = applyMiddleware(thunkMiddleware)
    // 利用store来保存状态(state)
    const store = createStore(reducer, storeEnhancer)
 */
/**
 * 注意点:
 *      如果导入的是redux-thunk,那么返回的是一个中间件对象
 *      如果导入的是redux-saga,那么返回的是一个用于创建中间件对象的方法
 */

import createSagaMiddleware from 'redux-saga'
// 通过createSagaMiddleware方法创建saga中间件对象
const sagaMiddleware = createSagaMiddleware()
// 创建store之前,通过applyMiddleware方法,告诉Redux需要应用哪些中间件
const storeEnhancer = applyMiddleware(sagaMiddleware)
const store = createStore(reducer, storeEnhancer)
/**
 * 注意点:
 *    如果是redux-thunk, 那么在创建store的时候指定完中间件即可
       如果是redux-saga, 那么除了需要在创建store的时候指定中间件以外, 还需要手动的调用中间件的run方法才行
 */
// 我们可以利用传入的生成器告诉redux-saga, 需要拦截哪些dispatch派发的action
sagaMiddleware.run(MySaga)

export default store

action

import { ADD_COUNT, CHANGE_ACTION, GET_USER_INFO } from "./constants"
export const addAction = (num) => {
    console.log('num', num)
    return {
        type: ADD_COUNT,
        num
    }
}
export const changeAction = (info) => {
    return {
        type: CHANGE_ACTION,
        info
    }
}
export const getInfo = (dispatch, getState) => {
    fetch('http://127.0.0.1:7001/info')
        .then((res) => {
            return res.json()
        })
        .then((data) => {
            dispatch(changeAction(data))
        })
        .catch(error => {
            console.log('error', error)
        })
}
export const getUserInfo = () => {
    return {
        type: GET_USER_INFO
    }
}

constant.js

export const ADD_COUNT = 'ADD_COUNT'
export const CHANGE_ACTION = 'CHANGE_ACTION'
export const GET_USER_INFO = 'GET_UER_INFO'

reducer.js

import { ADD_COUNT, CHANGE_ACTION } from "./constants"
const initialState = {
    count: 1,
    info: {}
}
const reducer = (state = initialState, action) => {
    switch (action.type) {
        case ADD_COUNT:
            return { ...state, count: state.count + action.num }
        case CHANGE_ACTION:
            return { ...state, info: action.info }
        default:
            return state
    }
}
export default reducer

saga.js

import { put, takeEvery } from 'redux-saga/effects'
import { GET_USER_INFO } from './constants'
import { changeAction } from './action'
// 获取网络数据
function* MyHandler() {
    const data = yield fetch('http://127.0.0.1:7001/info')
        .then((res) => {
            return res.json()
        })
        .catch(error => {
            console.log('error', error)
        })
    // 保存获取到的数据
    // 相当于 store.dispatch(changeAction());
    yield put(changeAction(data))
}
function* MySaga() {
    // GET_USER_INFO:指定需要拦截的action类型
    // MyHandler:指定拦截到这个类型的action之后交给谁来处理
    yield takeEvery(GET_USER_INFO, MyHandler)
}
export default MySaga

connect.js

import React from "react"
import store from '../index'
function connect(mapStateToProp, mapStateToAction) {
    return function enhanceComponent(WrapComponent) {
        class AdvComponent extends React.PureComponent {
            constructor (props) {
                super(props)
                console.log('this/props', this.props)
                this.state = {
                    ...mapStateToProp(store.getState())
                }
            }
            componentDidMount() {
                store.subscribe(() => {
                    this.setState({
                        ...mapStateToProp(store.getState())
                    })
                })
            }
            render() {
                return (
                    <WrapComponent {...this.props} {...this.state} {...mapStateToAction(store.dispatch)}></WrapComponent>
                )
            }
        }
        return AdvComponent
    }

}
export default connect

原理:高阶组件(传入一个组件,返回一个组件)。connect将传入的【mapStateToProps: 需要将store中保存的哪些数据映射到当前组件【传入的组件】的props上、mapDispatchToProps: 需要将哪些派发的任务映射到当前组件【传入的组件】的props上】,然后返回一个新的组件

调用

import React from "react"
import { connect } from "react-redux"
// import connect from "../../store/connect/connect"
import { addAction, getInfo, getUserInfo } from '../../store/action'

class About extends React.PureComponent {
    componentDidMount() {
        // this.props.getInfo()
        this.props.changeInfo()
    }
    render() {
        return (
            <div id={'about'}>
                About-----------
                <div>{this.props.count}</div>
                <button onClick={() => this.props.increment()}>++</button>
                <div>{this.props.info.name}</div>
                <div>{this.props.info.age}</div>
            </div>
        )
    }
}
// 在mapStateToProps方法中告诉React-Redux, 需要将store中保存的哪些数据映射到当前组件的props上
const mapStateToProps = (state) => {
    return {
        count: state.count,
        info: state.info
    }
}
// 在mapDispatchToProps方法中告诉React-Redux, 需要将哪些派发的任务映射到当前组件的props上
const mapDispatchToProps = (dispatch) => {
    return {
        increment() {
            dispatch(addAction(1))
        },
        getInfo() {
            // redux-thunk中间件作用:
            // 可以让dispatch方法可以接收一个函数, 可以让我们在通过dispatch派发任务的时候去执行我们传入的方法
            dispatch(getInfo)
        },
        changeInfo() {
            // redux-saga 正常派发,指定的类型会被拦截
            dispatch(getUserInfo())
        }
    }
}
export default connect(mapStateToProps, mapDispatchToProps)(About)

all

拦截多个类型的action, 那么我们就必须借助另外一个函数, all()

import {takeEvery, takeLatest, put, all} from 'redux-saga/effects'
import {GET_USER_INFO, ADD_COUNT, SUB_COUNT} from './constants';
import {changeAction} from './action';

function *myHandler() {
    // 获取网络数据
    const data1 = yield fetch('http://127.0.0.1:7001/info')
        .then((response)=>{
            return response.json();
        })
        .catch((error)=>{
            console.log(error);
        });
    const data2 = yield fetch('http://127.0.0.1:7001/info')
        .then((response)=>{
            return response.json();
        })
        .catch((error)=>{
            console.log(error);
        });
    /*
    如果我们只需要保存一个数据, 那么直接通过 yield put 即可
    但是如果我们想同时保存多个数据 , 那么我们就必须借助另外一个函数, all()
    * */
    // yield put(changeAction(data));
    yield all([
        yield put(changeUserAction(data1)),
        yield put(changeInfoAction(data2)),
        yield put({type:'CHANGE_USER_NAME', name: data1.name}),
        yield put({type:'CHANGE_USER_Age', name: data1.age}),
    ])
    console.log('执行到了监听方法的最后', data);
}
function *mySaga() {
    /*
    takeEvery和takeLatest区别: 是否能够完整的执行监听方法
    对于takeEvery而言, 每次拦截到对应类型的action, 都会完整的执行监听方法
    对于takeLatest而言, 每次拦截到对应类型的action, 都不能保证一定能够完整的执行监听方法

    例如: 连续派发了3次GET_USER_INFO的action,
         那么对于takeEvery而言, myHandler就会被完整的执行3次
         那么对于takeLatest而言, 如果派发下一次同类型action的时候
         上一次派发的action还没有处理完, 也就是上一次的监听方法还没有处理完
         那么takeLatest会放弃还没有处理完的代码, 直接开始处理下一次的action
    * */
    // yield takeEvery(GET_USER_INFO, myHandler)
    // yield takeLatest(GET_USER_INFO, myHandler)
    /*
    如果我们只需要拦截一个类型的action, 那么直接通过 yield takeEvery / yield takeLatest即可
    但是如果我们想同时拦截多个类型的action, 那么我们就必须借助另外一个函数, all()
    * */
    yield all([
        yield takeEvery(GET_USER_INFO, myHandler),
        yield takeEvery(ADD_COUNT, myHandler),
        yield takeEvery(SUB_COUNT, myHandler),
    ]);
}
export default mySaga;

Redux-DevTools的用法

import { createStore, applyMiddleware, compose } from "redux"
import reducer from "./reducer"
import MySaga from "./saga"
/**
 * redux-thunk
    import thunkMiddleware from "redux-thunk"
    
    // 创建store之前,通过applyMiddleware方法,告诉Redux需要引用哪些中间件
    const storeEnhancer = applyMiddleware(thunkMiddleware)
    // 利用store来保存状态(state)
    const store = createStore(reducer, storeEnhancer)
 */
/**
 * 注意点:
 *      如果导入的是redux-thunk,那么返回的是一个中间件对象
 *      如果导入的是redux-saga,那么返回的是一个用于创建中间件对象的方法
 */

import createSagaMiddleware from 'redux-saga'
// 通过createSagaMiddleware方法创建saga中间件对象
const sagaMiddleware = createSagaMiddleware()
// 创建store之前,通过applyMiddleware方法,告诉Redux需要应用哪些中间件
const storeEnhancer = applyMiddleware(sagaMiddleware)
// 进行Redux DevTools配置
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose
const store = createStore(reducer, composeEnhancer(storeEnhancer))
/**
 * 注意点:
 *    如果是redux-thunk, 那么在创建store的时候指定完中间件即可
       如果是redux-saga, 那么除了需要在创建store的时候指定中间件以外, 还需要手动的调用中间件的run方法才行
 */
// 我们可以利用传入的生成器告诉redux-saga, 需要拦截哪些dispatch派发的action
sagaMiddleware.run(MySaga)

export default store

学习笔记