Redux中间件与异步操作

2 篇文章 0 订阅
1 篇文章 0 订阅

一、中间件的概念与用法

中间件就是一个函数,对 store.dispatch 方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。

redux-logger模块

import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();

const store = createStore(reducer,applyMiddleware(logger));

上面代码中,redux-logger 提供一个生成器 createLogger,可以生成日志中间件 logger。然后,将它放在 applyMiddleware 方法之中,传入 createStore 方法,就完成了 store.dispatch()的功能增强。

这里有两点需要注意:

  • createStore方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware就是第三个参数了。
const store = createStore(
	reducer,
	initial_state,
	applyMiddleware(logger)
);
  • 中间件的次序有讲究。
const store = createStore(
	reducer,
	applyMiddleware(thunk, promise, logger)
);

上面代码中,applyMiddleware 方法的三个参数,就是三个中间件。有的中间件有次序要求,使用前要查一下文档。比如,logger 就一定要放在最后,否则输出结果会不正确。

三、applyMiddlewares()

applyMiddlewaresRedux 的原生方法,作用是将所有中间件组成一个数组

applyMiddlewares 源码

export default function applyMiddleware(...middlewares) {
	return (createStore) => (reducer, preloadedState, enhancer) => {
		var store = createStore(reducer, preloadedState, enhancer);
		var dispatch = store.dispatch;
		var chain = [];

		var middlewareAPI = {
			getState: store.getState,
			dispatch: (action) => dispatch(action)
		};
		chain = middlewares.map(middleware => middleware(middlewareAPI));
		dispatch = compose(...chain)(store.dispatch);

		return {...store, dispatch}
	}
}

上面代码中,所有中间件被放进了一个数组 chain,然后嵌套执行,最后执行 store.dispatch。可以看到,中间件内部(middlewareAPI)可以拿到 getStatedispatch 这两个方法。

四、异步操作的基本思路

同步操作只要发出一种 Action 即可,异步操作的差别是它要发出三种 Action。

  • 操作发起时的 Action
  • 操作成功时的 Action
  • 操作失败时的 Action

异步操作的思路

  • 操作开始时,送出一个 Action,触发 State 更新为"正在操作"状态,View 重新渲染
  • 操作结束后,再送出一个 Action,触发 State 更新为"操作结束"状态,View 再一次重新渲染

五、redux-thunk 中间件

Action 是由 store.dispatch 方法发送的。而 store.dispatch 方法正常情况下,参数只能是对象,不能是函数。这时,就要使用中间件 redux-thunk

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';

const store = createStore(
	reducer,
	applyMiddleware(thunk)
);

上面代码使用 redux-thunk 中间件,改造 store.dispatch,使得后者可以接受函数作为参数。因此,异步操作的解决方案就是,写出一个返回函数的 Action Creator,然后使用 redux-thunk 中间件改造 store.dispatch

注意:在 actionCreators 里可以返回一个函数,函数里有参数 dispatch,在函数内部去发送异步请求,异步请求回来之后,再分发 action2, action2 去更改 store 中的数据。

redux-thunk 安装

$ yarn add redux-thunk

$ cnpm install --save redux-thunk

redux-thunk 示例

Article.jsx

//[src/pages/Article.jsx]
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {changeMsg,getDataAxAction} from '../store/actionCreators/articleAction';
class Article extends Component {
    render() {
        return (
            <div>
                {JSON.stringify(this.props)}
                <button onClick={this.props.changeMsg}>更改msg-同步</button>
                <button onClick={this.props.getData}>异步获取数据</button>
            </div>
        );
    }
}
 
const mapStateToProps = (state,ownProps) =>{
    return state;
}
const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        changeMsg:()=>{
            //分发action
            dispatch(changeMsg('更改后的数据'));
        },
        getData:()=>{
            //分发action thunk
            dispatch(getDataAxAction({page:0,pageSize:10}));            
        }
    }
}
export default connect(mapStateToProps, mapDispatchToProps)(Article);

articleAction.js

//[src/store/actionCreators/articleAction.js]
import {CHANGE_MSG,CHANGE_ARTICLES} from '../actionTypes';
import axios from 'axios';

//同步操作
export const changeMsg = (value) =>{
    return {
        type: CHANGE_MSG,
        value
    };
}

//redux-thunk actionCreator   异步操作
export const getDataAxAction = (value)=>{
    return (dispatch)=>{
        axios.get('http://134.175.154.93:8099/manager/article/findArticle',{params:value}).then((res)=>{
            //res.data.data.list 需要获取的数组
            dispatch(changeArticles(res.data.data.list));
        }).catch((err)=>{
            console.log(err);
        });
    }
}

//更改store中的articleCreator  
const changeArticles = (value)=>{
    return {
        type:CHANGE_ARTICLES,
        value
    }
}

articleReducer.js

//[src/store/reducers/articleReducer.js]
import {CHANGE_MSG,CHANGE_ARTICLES} from '../actionTypes';
let initState = {
    msg:'article',
    articles:[]
};
export default (state=initState,action)=>{
    if (action.type === CHANGE_MSG) {
        return {
            ...state,
            msg:action.value
        }
    }
    if (action.type === CHANGE_ARTICLES) {
        return {
            ...state,
            articles:action.value
        }
    }
    return state;
}

index.js

//[src/store/reducers/index.js]
//合并
import {combineReducers} from 'redux';
import article from './articleReducer';
export default combineReducers({
    article
});

actionTypes.js

//[src/store/actionTypes.js]
export const CHANGE_MSG = 'CHANGE_MSG';
export const CHANGE_ARTICLES = 'CHANGE_ARTICLES';

index.js

//[src/store/index.js]
//创建store
//引入中间件    redux-thunk
import {createStore,applyMiddleware,compose} from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';

//下面两行代码是为了能让 DevTools 正常使用
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(applyMiddleware(thunk));

//将reducer与store绑定,创建store并返回
let store = createStore(reducer,enhancer);
export default store; 

App.js

//[src/App.js]
import React from 'react';
import './App.css';
import Article from './pages/Article';

function App() {
  return (
    <div>
      <Article/>
    </div>
  );
}

export default App;

index.js

//[src/index.js]
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {Provider} from 'react-redux';
import store from './store';

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

serviceWorker.unregister();

六、redux-saga 中间件

redux-saga 是一个 redux 的中间件,而中间件的作用是为 redux 提供额外的功能。

但是在实际的应用开发中,我们希望做一些异步的(如Ajax请求)且不纯粹的操作(如改变外部的状态),这些在函数式编程范式中被称为“副作用”。redux-saga 就是用来处理上述副作用(异步任务)的一个中间件。它是一个接收事件,并可能触发新事件的过程管理者,为你的应用管理复杂的流程。

import {createStore,applyMiddleware} from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducers';
import mySagas from './mySagas';

//创建saga中间件
const sagaMiddleware = createSagaMiddleware();
let store = createStore(reducer, applyMiddleware(sagaMiddleware));

//使用mySagas
sagaMiddleware.run(mySagas);
export default store;

redux-saga 工作原理

  • sages 采用 Generator 函数来 yield Effects(包含指令的文本对象)
  • Generator 函数的作用是可以暂停执行,再次执行的时候从上次暂停的地方继续执行
  • Effect 是一个简单的对象,该对象包含了一些给 middleware 解释执行的信息
  • 可以通过使用 effects API 如 fork,call,take,put,cancel 等来创建 Effect。

redux-saga 分类

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

注意:使用 generator 函数在sagas文件中共处理业务逻辑,有了 redux-saga 之后,除了 reducer可以接收 actionsagas 也可以接收并做业务逻辑处理。

redux-saga 安装

$ yarn add redux-saga

$ cnpm install --save redux-saga

redux-saga 示例

Article.jsx

//[src/pages/Article.jsx]
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {changeMsg,getDataSaAction,deleteDataSaAction} from '../store/actionCreators/articleAction';
class Article extends Component {
    render() {
        return (
            <div>
                {JSON.stringify(this.props)}
                <button onClick={this.props.changeMsg}>更改msg-同步</button>
                <button onClick={this.props.getData}>异步获取数据</button>
                <button onClick={this.props.deleteDataById}>异步删除数据</button>
            </div>
        );
    }
}
 
const mapStateToProps = (state,ownProps) =>{
    return state;
}
const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        changeMsg:()=>{
            //分发action
            dispatch(changeMsg('更改后的数据'));
        },
        getData:()=>{
            //分发action saga
            dispatch(getDataSaAction({ page: 0, pageSize: 10 }));

        },
        deleteDataById:()=>{
            //分发action saga
            dispatch(deleteDataSaAction({id:4062}));
        }
    }
}
export default connect(mapStateToProps, mapDispatchToProps)(Article);

articleAction.js

//[src/store/actionCreators/articleAction.js]
import {CHANGE_MSG,CHANGE_ARTICLES,GET_DATA_SA_ACTION,DELETE_DATA_SA_ACTION} from '../actionTypes';
export const changeMsg = (value) =>{
    return {
        type: CHANGE_MSG,
        value
    };
}

export const changeArticles = (value) => {
    return {
        type:CHANGE_ARTICLES,
        value
    }
}

//redux-saga的actionCreator
export const getDataSaAction = (value) =>{
    return {
        type:GET_DATA_SA_ACTION,
        value
    }
}

//删除数据
export const deleteDataSaAction = (value) =>{
    return {
        type:DELETE_DATA_SA_ACTION,
        value
    }
}

articleReducer.js

//[src/store/reducers/articleReducer.js]
import {CHANGE_MSG,CHANGE_ARTICLES} from '../actionTypes';
let initState = {
    msg:'article',
    articles:[]
};
export default (state=initState,action)=>{
    if (action.type === CHANGE_MSG) {
        return {
            ...state,
            msg:action.value
        }
    }
    if (action.type === CHANGE_ARTICLES) {
        return {
            ...state,
            articles:action.value
        }
    }
    return state;
}

index.js

//[src/store/reducers/index.js]
//合并
import {combineReducers} from 'redux';
import article from './articleReducer';
export default combineReducers({
    article
});

actionTypes.js

//[src/store/actionTypes.js]
export const CHANGE_MSG = 'CHANGE_MSG';
export const CHANGE_ARTICLES = 'CHANGE_ARTICLES';
export const GET_DATA_SA_ACTION = 'GET_DATA_SA_ACTION';
export const DELETE_DATA_SA_ACTION = 'DELETE_DATA_SA_ACTION';

index.js

//[src/store/index.js]
//创建store
import {createStore,applyMiddleware,compose} from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducers';
import mySagas from './mySagas';

//为了使DevTools正常使用
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;

//创建saga中间件
const sagaMiddleware = createSagaMiddleware();
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware))

let store = createStore(reducer,enhancer);
//使用mySagas
sagaMiddleware.run(mySagas);
export default store;

mySagas.js

//[src/store/mySagas.js]
//处理业务逻辑
//如果发送action这里可以监听,然后异步处理
import {takeEvery,put} from 'redux-saga/effects';
import {GET_DATA_SA_ACTION,DELETE_DATA_SA_ACTION} from './actionTypes';
import {changeArticles,getDataSaAction} from './actionCreators/articleAction'
import axios from 'axios';
function* mySagas(params) {
    //takeEvery监听某个action,然后执行第二个参数的generator函数
    yield takeEvery(GET_DATA_SA_ACTION, getList);
    yield takeEvery(DELETE_DATA_SA_ACTION, deleteData);
}
//参数action就是对应的action对象
function* getList(action){
    //发送异步,异步成功之后分发action
    let res = yield axios.get('http://134.175.154.93:8099/manager/article/findArticle', {
        params: action.value
    });
    //put就是转发action,类似于dispatch
    yield put(changeArticles(res.data.data.list));
}
function* deleteData(action) {
    let res = yield axios.get('http://134.175.154.93:8099/manager/article/deleteArticleById', {
        params: action.value
    });
    //put就是转发action,类似于dispatch
    yield put(getDataSaAction({ page: 0, pageSize: 10 }));
}
export default mySagas;

App.js

//[src/App.js]
import React from 'react';
import './App.css';
import Article from './pages/Article';

function App() {
  return (
    <div>
      <Article/>
    </div>
  );
}

export default App;

index.js

//[src/index.js]
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {Provider} from 'react-redux';
import store from './store';

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

serviceWorker.unregister();

七、thunk 和 saga 的对比

在这里插入图片描述
redux-thunk 将原本的 action 对象,变成可执行函数后交给 reducer 处理,所以把异步业务逻辑进行封装。相比与 saga,后者就显得格外的繁琐, api 异常冗杂,其结果实际上是并行生成了 reducer 处理 action 。目前基本的业务场景,使用 react-redux 即可,不建议一开始就使用 saga,也不建议,最初就自己动手封装中间件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值