redux中间件

  • 官方文档

  • redux里,action仅仅是携带了数据的普通js对象action creator返回的值是这个action类型的对象。然后通过store.dispatch()进行分发。同步的情况下一切都很完美,也就是说,默认情况下,Redux 自身只能处理同步数据流。但是在实际项目开发中,状态的更新、获取,通常是使用异步操作来实现, 但是reducer无法处理异步的情况。那么我们就需要在actionreducer中间架起一座桥梁来处理异步。这就是middleware(中间件)。

  • 由此可见,在redux中,中间件的的核心功能就是:在 action 被发起之后,到达 reducer 之前的一些扩展,主要增强store.dispatch的能力

中间件的执行流程

image

由上图我们可以看出,所谓redux中间件其实就是指的是actionstore之间的流程加工。即dispatch的封装和升级。

中间件的作用以及触发时机

处理具有副作用(side effect)的功能,比如,异步操作就是最常见的 side effect

中间件说明:

  • 中间件,可以理解为处理一个功能的中间环节
  • 中间件的优势:可以串联、组合,在一个项目中使用多个中间件
  • Redux 中间件用来处理 状态 更新,也就是在 状态 更新的过程中,执行一系列的相应操作

中间件的触发时机

  • Redux 中间件执行时机:dispatching action 和 到达 reducer 之间

  • 没有中间件:dispatch(action) => reducer
    image

  • 使用中间件:dispatch(action) => 执行中间件代码 => reducer
    image

原理:

封装了 redux 自己的 dispatch 方法

  • 没有中间件:store.dispatch() 就是 Redux 库自己提供的原生 dispatch 方法,用来发起状态更新
  • 使用中间件:store.dispatch()就是中间件封装处理后的 dispatch,但是,最终一定会调用 Redux 自己的 dispatch 方法发起状态更新

redux-thunk

  • 官方文档
  • Redux-thunkredux 的中间件,它主要设计用来处理项目中的异步操作,如获取接口数据等,过去异步操作都会写在组件中,请求到数据再创建 action。这使得异步操作遍布很多组件文件,中,维护不便,且不易于自动化测试。
  • Redux-thunk 主要理念是:将所有异步操作都放在action里面去处理,与业务组件代码解耦,而要实现这个理念,需要 action 处理异步逻辑,需要它是一个函数。

实现逻辑

  • 原生的Redux中,action(actionCreator)内部返回的是一个对象,但是redux-thunk中间件可以处理函数形式的 action,由此我们就可以在这个函数形式的action中处理异步操作
  • React内部通过判断你action中返回的是一个对象还是一个函数来执行操作,如果是返回一个对象则直接调用dispatch来修改状态,如果是一个函数,则使用者可以执行决定调用dispatch的时机,即我们可以等待异步操作完成后在调用dispatch修改状态(redux-thunk会给我们在返回的函数中提供dispatch)
// 引入axios
import axios from 'axios'

/* 1. 原生的actionCreator形式创建的action */
function getDataAction(){
// 使用axios发送数据请求(异步操作)
axios.get('/xxx/xxx').then(res=>{
console.log('数据请求成功',res)

return { // 数据请求成功action后返回原生的对象
type:'getData',// 触发修改全局store中状态的字段
value:res,// 返回的数据
}
})
}
/*
此时项目会报错,因为是异步操作,也就是说,数据还没有回来之前,可能return已经执行了,
但是你的return是写在数据请求的回调函数中的,但是数据回来是需要时间的,此时你的原
函数早已执行完毕了,但是由于原函数没有return,那就是默认undefined,就会导致报错
*/

export default getDataAction // 将创建的action暴露出去

/* ------------------------------------------------------------------------ */

/* 2. 使用redux中间件结合actionCreator创建的action实现异步处理操作 */
function getDataAction2(){
// redux-thunk 写法则是支持在action中返回一个函数
/*
注意:此处返回的是一个函数,返回的函数有两个参数:
第一个参数:dispatch 函数,用来分发 action
第二个参数:getState 函数,用来获取 redux 状态
*/
return (dispatch,getState)=>{
// 使用axios发送数据请求(异步操作)
axios.get('/xxx/xxx').then(res=>{
console.log('数据请求成功!',res)
// 判断数据请求完成后再通过dispatch通知reducer修改状态
dispatch ({ // 数据请求成功action后返回原生的对象
type:'getData',// 触发修改全局store中状态的字段
value:res,// 返回的数据
})
})
}
}

export default getDataAction // 将创建的action暴露出去

使用流程

  1. 安装redux-thunk
npm i redux-thunk
  1. 在全局的store中配置中间件,类似于Vue中的Vue.use()
// 引入中间件, applyMiddleware()应用中间件
// 1. 引入redux
import {applyMiddleware, combineReducers, createStore} from 'redux'

// 2. 引入其他的小Reducer
import pageNameReducer from './reducer/pageNameReducer'//处理关于页面标题的reducer
import isShowBottomReducer from './reducer/isShowBottomReducer'//处理底部区域展示的reducer

// 3. 引入中间件
import ReduxThunk from 'redux-thunk'//引入redux-thunk中间件

// 4. 合并reducer
const reducer = combineReducers({
pageNameReducer,
isShowBottomReducer
})

/*
由于创建的state是不能直接放到创建的store中的, 需要通过reducer将数据添加到store中,
因此创建store时必须创建reducer;reducer函数的返回值, 会作为store之后存储的state,
只要调用dispatch就会重新执行reducer函数,reducer是一个纯函数,不可以直接修改state,
*/

// 5. 创建全局store
const store = createStore(reducer,applyMiddleware(ReduxThunk))//并应用中间件

// 6. 导出store
export default store
  1. actionCreator自定义action
// 接口地址:随机生成一个狗图(vpn):https://dog.ceo/api/breeds/image/random
// 1. 引入axios
import axios from 'axios'

/* 2. 使用redux中间件结合actionCreator创建的action实现异步处理操作 */
function getDataAction2(){
// redux-thunk 写法则是支持在action中返回一个函数
/*
注意:此处返回的是一个函数,返回的函数有两个参数:
第一个参数:dispatch 函数,用来分发 action
第二个参数:getState 函数,用来获取 redux 状态
*/
return (dispatch,getState)=>{// 处理异步操作,等待数据返回成功在执行dispatch操作(相当于转为同步)
// 使用axios发送数据请求(异步操作)
axios.get('https://dog.ceo/api/breeds/image/random').then(res=>{
console.log('数据请求成功!',res)
//判断数据请求完成后再通过dispatch通知reducer修改状态
dispatch ({ // 数据请求成功action后返回原生的对象
type:'getData',// 触发修改全局store中状态的字段
value:res.data.message,// 返回的数据
})
})
}
}

export default getDataAction2 // 将创建的action暴露出去
  1. home.js(home组件)
import React, { Component } from 'react'
// 引入路由相关的组件
import { HashRouter as Router, Route, Redirect, Switch, NavLink } from 'react-router-dom'
import store from '../../store/index.js'//引入全局store
// 引入对应的组件
import About from './about.js'//关于页
import Info from './info.js'//个人信息页
import Footer from './footer.js'//底部区域
import NotFound from './404.js'//404页面

export default class home extends Component {
componentDidMount(){
/*
通过store.subscribe()函数可以监听store中的数据变化,
store.subscribe()函数的参数接收一个函数, 该函数在store数据发生更新自动回调
*/

store.subscribe(()=>{
console.log('home组件监听中....');
// 修改组件中state的状态重新渲染组件
this.setState({
// 使用reducer合并则需要多获取对应的小reducer在获取对应的状态
isShow:store.getState().isShowBottomReducer.isShow
})
})
}
state = {
// 我们可以在其他文件中通过 store.getState 来获取当前的state;
// 使用reducer合并则需要多获取对应的小reducer在获取对应的状态
isShow:store.getState().isShowBottomReducer.isShow // 获取全局store中的状态
}
render() {
console.log('是否展示底部区域',store.getState());
return (
<div>
<h1>首页</h1>
<p>(去到信息页关闭底部,去到关于页开启底部)</p>
<div style={{ width: '100%', height: '400px', backgroundColor: 'yellowgreen' }}>
<Router>
<ul>
<li><NavLink to={'/home/info'}>info</NavLink></li>
<li><NavLink to={'/home/about'}>about</NavLink></li>
</ul>
{/* 使用Switch来解决每次页面刷新,重定向功能执行的bug(模糊匹配) */}
<Switch>
<Route path="/home/about" component={About} />
<Route path="/home/info" component={Info} />

<Redirect from="/" to="/home/info" exact />
{/* 匹配不到的页面则展示404 */}
<Route component={NotFound} />
</Switch>
</Router>
</div>
{/* 通过判断全局store中的isShow来执行是否展示Footer组件 */}
{this.state.isShow && <Footer />}
</div>
)
}
}
  1. about.js(展示图片的组件)
import React ,{useEffect,useState}from 'react'
import store from '../../store/index.js'//引入全局store
import getDataAction from '../../store/actionCreator/getDataAction.js';

export default function About() {
// 使用reducer合并则需要多获取对应的小reducer在获取对应的状态
console.log('组件创建时全局store中的图片数据',store.getState().dataReducer.data);
// 模拟生命周期
useEffect(()=>{//组件挂载
console.log('进入About组件');
// 判断全局store中是否有图片数据
if(store.getState().dataReducer.data === ''){
store.dispatch(getDataAction())
console.log('全局store中没有数据!');
}else{
console.log('使用缓存中的数据!');
}

return ()=>{//组件销毁
console.log('退出About组件');
// 离开About组件修改全局store中的payload为false(关闭底部区域)
unsubscribe()//取消订阅
}
},[])
// 使用hooks创建调用state
// 使用reducer合并则需要多获取对应的小reducer在获取对应的状态
const [img,setImg] = useState(store.getState().dataReducer.data)

// 我们也可以在对应的组件中设置store订阅(设置订阅与取消订阅固定形式)
var unsubscribe = store.subscribe(()=>{
console.log('about组件订阅中.....',store.getState().dataReducer.data);
})

return (
// 展示全局store中的pageName
<div>
<img src={`${img}`} alt=""/>
</div>
)
}

结果展示:

image