redux中间件是什么?
理解redux中间件首先我们需要理解redux是什么, Redux是JavaScript应⽤的状态容器。它保证程序⾏为⼀致性且易于测试。当业务足够复杂时,我们就需要使用redux来存储我们的多页面共同数据
redux的使用范例:
//src/store/index.js
function countReducer(state = 0, action) {
switch (action.type) {
case "ADD":
return state + (action.payload ?? 1);
default:
return state;
}
}
const store = createStore(countReducer);
export default store;
// pages/Reduxpage.js 页面使用
import store from "../store";
export default class ReduxPage extends Component {
add = () => {
//*重点 通过dispatch改变数据
store.dispatch({type: "ADD"});
};
render() {
return (
<div>
<h3>{store.getState()}</h3>
<button onClick={this.add}>add</button>
</div>
)
}
}
而我们工作中常常发现单纯的通过dispatch传递带type属性的对象修改store 内的数据远远满足不了我们的需求,此时redux中间件applyMiddleware登场了
applyMiddleware的使用范例:
//src/store/index.js
import { createStore, applyMiddleware } from "redux"
import thunk from "redux-thunk"; // 异步解决方案
import logger from "redux-logger"; // 打印日志
import promise from "redux-promise"; // 处理promise
function countReducer(state = 0, action) {
//...同上
}
const store = createStore(
countReducer,
applyMiddleware(promise, thunk, logger)
)
export default store;
此时我们发现createStore内多了第二个参数applyMiddleware函数的返回值,那么它是干嘛的呢,它返回的是什么呢,理解了它,redux中间件原理你就掌握了, 首先我们来简单说明一下它的作用:利用 compose 写法 将thunk,logger,promise 这3个中间件组合成一个柯里化版本的 dispatch 方法 ,此方法替换掉了原始的dispatch 方法 接下面我们来看页面是怎么工作的
// pages/Reduxpage.js 页面使用
import store from "../store";
export default class ReduxPage extends Component {
add = () => {
store.dispatch({type: "ADD"})
}
//模拟异步请求数据更改store
asyAdd = () => {
store.dispatch((dispatch, getState) => {
setTimeout(() => {
dispatch({type: "ADD"})
}, 1000)
})
}
//模拟Promise更改store
promiseMinus = () => {
store.dispatch(
Promise.resolve({type: "ADD", payload: 3})
)
}
render() {
return (
<div>
<h3>store改变的数据:{store.getState()}</h3>
<button onClick={this.add}> 同步改变数据 </button>
<button onClick={this.asyAdd}> 异步请求数据 </button>
<button onClick={this.promiseMinus}> promise返回数据 </button>
</div>
)
}
}
可见
applyMiddleware的作用就是将dispatch内传递的参数多元化,让dispatch不再是那个只能传递带type属性的对象了, 此时dispatch不只是dispatch, 它可以接收函数,可以接收promise, 可以接收异步请求,而这都可以通过给applyMiddleware传递对应的处理函数为参数实现不同的action,其目的就是将最终得到的结果传递给原始dispatch, 通过原始dispatch改变store更新数据, 这就是中间件的最终意义。
既然理解了中间件的原理, 那我们不妨简单实现它!
实现createStore, applyMiddleware架构
实现逻辑存放在myRedux文件夹内:
./myRedux
——applyMiddleware.js //实现applyMiddleware强化dispatch方法
——createStore.js //实现一个简单的createStore方法
——index.js //导出
./store
——index.js
1. 实现createStore方法:
export default function createStore(renduer, enhancer) {
//如果添加了中间件就需要增强createStore
if (enhancer) {
return enhancer(createStore)(renduer)
}
let currentState
let currentListeners = []
function getState() {
return currentState
}
function dispatch(action) {
//修改state
currentState = renduer(currentState, action)
// 订阅通知
currentListeners.forEach(listener => listener())
}
function subscribe(listener) {
//添加订阅
currentListeners.push(listener)
return () => {
currentListeners = []
}
}
// 获取初始值 (原理:没有此type 走default: return state)
dispatch({type: "REDUX/YYYYYYYYYY"})
return { getState, dispatch, subscribe }
}
2. 实现applyMiddleware方法:
export default function applyMiddleware(...middlewares) {
return createStore => reducer => {
const store = createStore(reducer) //返回原始{ getState, dispatch, subscribe }
let dispatch = store.dispatch
const midApi = {
getState: store.getState,
dispatch: action => dispatch(action) //dispatch随自身变化 并非是原始的store.dispatch
}
//*重点 midApic传递的dispatch是下面增强后的dispatch
const middlewareChain = middlewares.map(middleware => middleware(midApi))
dispatch = compose(...middlewareChain)(store.dispatch)
return { ...store, dispatch }
}
}
//传入compose(a, b, c)(...args) 返回: a(b(c(...args)))
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
compose原理是什么, 什么时候该用compose?
答: 当一个值需要经过多个函数才能变成另外一个值,就可以把所有中间件合并成一个函数, 这叫做函数合成(compose)
3. myRedux/index.js来导出模块:
//index.js
import createStore from "./createStore"
import applyMiddleware from "./applyMiddleware"
export {
createStore,
applyMiddleware
}
4. store/index.js文件导出store:
// store/index.js
// 官方redux
// import { createStore, applyMiddleware } from "redux"
// import thunk from "redux-thunk"; // 异步解决方案
// import logger from "redux-logger"; // 打印日志
// import promise from "redux-promise"; // 处理promise
// 自写redux (我们使用自写的myRedux)
import { createStore, applyMiddleware } from "../myRedux"
function countReducer(state = 0, action) {
switch(action.type) {
case "ADD":
return state + (action.payload ?? 1);
case "MINUS":
return state - action.payload || 1;
default:
return state
}
}
const store = createStore(
countReducer,
applyMiddleware(promise, thunk, logger)
)
export default store
/********下面是自己实现的promise, thunk, logger三个中间件********/
// 以下的所有 action 都为页面 dispatch(action) 传递过来的 action
function logger({dispatch, getState}) {
return next => action => {
console.log("++++++++++++++++++++++++++")
console.log(action.type + "执行了!!!")
const prevState = getState()
console.log("prev state", prevState)
const returnValue = next(action) //因为logger是最后一个参数 所以next是 "dispatch = compose(...middlewareChain)(store.dispatch);" 的store.dispatch (就是原版dispatch)
const nextState = getState()
console.log("prev state", nextState)
console.log("++++++++++++++++++++++++++")
return returnValue;
}
}
function thunk({dispatch, getState}) {
return next => action => {
console.log("thunk", action)
// action 数据类型是?对象 | 函数
if (typeof action === "function") {
return action(dispatch, getState)
}
// 不是 对象|函数 执行next() next是前一个函数的返回函数
// 比如applyMiddleware(promise, thunk, logger) thunk的前一个函数是logger 所以next是logger内的以action为参数的箭头函数
return next(action)
}
}
function promise({dispatch}) {
function isPromise(obj){
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}
return next => action => { //此时这个 action 就是 加强版的 dispatch()内的参数
console.log("promise", action)
// 不是promise执行next() next是前一个函数的返回函数
// 比如applyMiddleware(promise, thunk, logger) promise的前一个函数是thunk 所以next是thunk内的以action为参数的箭头函数
return isPromise(action) ? action.then(dispatch) : next(action)
}
}
5. 页面引入并使用:
// /pages/ReduxPage.js
import React, { Component } from "react"
import store from "../store"
export default class ReduxPage extends Component {
componentDidMount() {
//监听store改变刷新页面
this.unsubscribe = store.subscribe(() => {
this.forceUpdate(); //强制刷新页面
})
}
componentWillUnmount() {
//组件卸载之前销毁store监听
if (this.unsubscribe) {
this.unsubscribe()
}
}
add = () => {
store.dispatch({type: "ADD"})
}
asyAdd = () => {
store.dispatch((dispatch, getState) => {
setTimeout(() => {
dispatch({type: "ADD"})
}, 1000)
})
}
promiseMinus = () => {
store.dispatch(
Promise.resolve({type: "ADD", payload: 3})
)
}
render() {
return (
<div>
<h3>ReduxPage改变的数据count:{store.getState()}</h3>
<button onClick={this.add}> 同步改变数据 </button>
<button onClick={this.asyAdd}> 异步请求数据 </button>
<button onClick={this.promiseMinus}> promise返回数据 </button>
</div>
)
}
}
你们知道真强后的dispatch方法是什么样子吗? 我这有个参考:伪代码如下:
//中间件顺序为 applyMiddleware(promise, thunk, logger) 时compose强化后的dispatch模样:
//内部的 dispatch, getState 就是 midApi 传递进去的函数 dispatch就是强化后的dispatch (内部的dispatch类似递归)
function dispatch(action) {
console.log("promise", action)
return isPromise(action) ? action.then(dispatch) : thunkNext(action);
function thunkNext(action) {
console.log("thunk", action)
if (typeof action === "function") {
return action(dispatch, getState);
}
return loggerNext(action)
}
function loggerNext(action) {
const prevState = getState();
console.log("prev state", prevState)
const returnValue = next(action); //最后的这个next就是 store.dispatch
const nextState = getState();
console.log("prev state", nextState)
return returnValue;
}
}
//页面触发:可带入上面dispatch试试执行流程
//1. 同步改变数据
dispatch({type: "ADD"})
//2. 异步请求触发
dispatch((dispatch, getState) => {
setTimeout(() => {
dispatch({type: "ADD"});
}, 1000);
})
//3. promise返回数据
dispatch(
Promise.resolve({
type: "MINUS",
payload: 100
})
);
此时我们点击3个按钮会出现什么:
applyMiddleware(promise, thunk, logger)内中间件的匹配顺序是:promise → thunk → logger从左往右。
可见当前中间件不符合匹配规则时就会将dispatch内参数传递给后一个中间件去处理, 如果不行就继续向后传递以此类推, dispatch参数如果是一个方法 其内部的dispatch执行又会递归执行dispatch以确保内部dispatch参数被中间件化,最终所有中间件都无法匹配时 才会执行初始传入的原始dispatch更新数据