Redux中间件及异步操作
中间件的概念
如果要添加redux的异步功能,你会在哪个环境添加?
(1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。
(2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。
(3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。
只有发送 Action 的这个步骤,即store.dispatch()
方法,可以添加功能。
中间件就是一个函数,对store.dispatch
方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。
中间件 redux-thunk
store.dispatch
方法正常情况下,参数只能是对象,不能是函数。
这时,就要使用中间件redux-thunk
因此,异步操作的第一种解决方案就是,写出一个返回函数的 Action Creator,然后使用redux-thunk
中间件改造store.dispatch
。
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';
// Note: this API requires redux@>=3.1.0
const store = createStore(
reducer,
applyMiddleware(thunk)
);
使用redux和react-thunk进行异步操作
1. 安装应用
安装redux、react-redux、react-thunk以及axios
yarn add redux react-redux react-thunk axios
2. axios配置
在utils/axios
中配置如下:
import axios from 'axios'
axios.defaults.baseURL = 'https://api-hmugo-web.itheima.net/api/public/v1'
// 请求拦截器
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// 响应拦截器
axios.interceptors.response.use(function (response) {
if(response.status===200){
return response.data;
}
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
export default axios;
3. 封装actionType
文件store/actionType/index.js
:
export const TODO_SYNC = 'TODO_SYNC'
4. 创建actionCreator
文件store/actionCreator/index.js
:
import { TODO_SYNC } from '../actionType';
import axios from '../../utils/axios'
export const receiveTodos = res=>{
return {
type: TODO_SYNC,
res
}
}
export const getTodos = ()=>{
return dispatch =>{
return axios.get('/home/swiperdata')
.then(res=>{
if(res.meta.status===200){
dispatch(receiveTodos(res.message))
}
})
.catch(err=>{
console.log(err);
})
}
}
5. 创建reducer
文件store/reducer/index.js
:
import { TODO_SYNC } from '../actionType';
const defaultState = {
msg: 'hello react'
}
export default (state=defaultState,action)=>{
switch (action.type) {
case TODO_SYNC:
return {
...state,
res:action.res
}
default:
return state;
}
}
6. 创建store
文件store/index.js
:
import { createStore,applyMiddleware } from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'
const store = createStore(reducer,applyMiddleware(thunk));
export default store;
7. 使用react-redux
项目入口文件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}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
8. 组件中发送异步请求
文件components/Test1.jsx
:
import React,{Component} from 'react'
import { connect } from 'react-redux'
import { getTodos } from '../store/actionCreator'
import store from '../store'
class Test1 extends Component{
componentDidMount(){
// store.dispatch(getTodos())
}
render(){
let {msg,getMsg,res} = this.props;
res = res?res:[];
return (
<div>
{msg}
<button onClick={getMsg}>发送请求</button>
<ul>
{
res.map(v=>(
<li key={v.goods_id}>
<img src={v.image_src} alt=""/>
</li>
))
}
</ul>
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
return {
msg: state.msg,
res: state.res
}
}
// const mapDispatchToProps = (dispatch, ownProps) => {
// return {
// getMsg: ()=>{
// dispatch(getTodos())
// }
// }
// }
const mapDispatchToProps = {
getMsg: ()=>getTodos()
}
export default connect(mapStateToProps,mapDispatchToProps)(Test1)
中间件 redux-saga
使用generator函数在sagas文件中处理业务逻辑,有了redux-saga之后,除了reducer可以接收action,sagas也可以接收并做业务逻辑处理。
-
put
put这个Effect方法跟redux原始的dispatch相似,都是可以发出action,且发出的action都会被reducer监听到。
yield put({ type: GET_MSG_SUCCESS, res })
-
takeEvery和takeLatest
takeEvery和takeLatest用于监听相应的动作并执行相应的方法。
takeEvery可以同时监听到多个相同的action。
与takeEvery不同的是,takeLatest是会监听执行最近的那个被触发的action。
takeEvery('login',loginFunc) takeLatest('login',loginFunc)
redux-saga安装与使用
1. 安装
yarn add redux-saga
2. 使用
store/actionType.js
:
export const GET_MSG_SUCCESS = 'GET_MSG_SUCCESS'
export const GET_MSG_START = 'GET_MSG_START'
store/reducer.js
:
import {GET_MSG_SUCCESS} from './actionType'
const defaultState = {
msg: 'hello saga'
}
export default (state=defaultState,action)=>{
if(action.type===GET_MSG_SUCCESS){
return {
...state,
res:action.res
}
}
return state;
}
store/sagas.js
:
import { takeEvery,put } from 'redux-saga/effects'
import {GET_MSG_SUCCESS,GET_MSG_START} from './actionType'
import axios from '../utils/axios'
function* mySaga(){
yield takeEvery(GET_MSG_START,getMsg)
}
function* getMsg(){
const res = yield axios.get('/home/swiperdata');
yield put({
type: GET_MSG_SUCCESS,
res
})
}
export default mySaga;
store/index.js
:
import { createStore,applyMiddleware } from 'redux'
import reducer from './reducer'
import createSagaMiddleware from 'redux-saga'
import mySagas from './sagas'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(reducer,applyMiddleware(sagaMiddleware))
sagaMiddleware.run(mySagas)
export default store
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 store from './store'
import {Provider} from 'react-redux';
ReactDOM.render(
<Provider store={store}>
{/* <React.StrictMode> */}
<App />
{/* </React.StrictMode> */}
</Provider>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
components/Test1.jsx
:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { GET_MSG_START } from '../store/actionType'
class Test1 extends Component {
componentDidMount(){
const { dispatch } = this.props;
dispatch({type:GET_MSG_START})
}
render() {
const {msg,res} = this.props;
console.log(res,'__res');
return (
<div>
{msg}
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return {
msg: state.msg,
res: state.res
}
}
export default connect(mapStateToProps)(Test1);